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


blog photo
Veröffentlicht von Joanne Snel am 22. 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 Client-Secret und Deiner Client-ID. 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.ID:[MessageHandler(Filters.text & ~Filters.regex('^/'),
8                                          TradingBot().get_client_id)],
9            TradingBot.SECRET:[MessageHandler(Filters.text & ~Filters.regex('^/'),
10                                              TradingBot().get_client_secret)],
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_client_id, get_client_secret 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 seine*ihre Client-ID eingibt, sieht das in etwa so aus:

1b2297358-e23a-4c0a-a156-c7804ef75c4a

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 "ID" befindet und eine Texteingabe empfangen wird, die nicht mit "/" beginnt, führst Du die Funktion get_client_id 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 Token(), 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, 0 oder 1. 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, seine*ihre Client-ID einzugeben und wechselt in den ersten Conversation state: ID. Wie wir in ConversationHandler.py gesehen haben, wird die Funktion get_client_id aufgerufen, sobald sich die Unterhaltung in dem ID state befindet. In dieser Funktion wird die Benutzereingabe in einem für den*die Nutzer:in eindeutigen Dictionary gespeichert. Der*Die Nutzer:in wird aufgefordert, sein*ihr Client-Secret einzugeben und die Konversation wird in den SECRET state überführt.

Sobald sich die Konversation in dem SECRET state befindet, wird die Funktion get_client_secret aufgerufen. Auch hier wird das Secret im benutzerspezifischen Dictionary gespeichert. Dann wird in einem try-except-block das Konto authentifiziert und der Zugriffs-Token abgerufen. Dies führt zu einem der drei möglichen Szenarien:

  • Wenn in der Antwort auf die Anfrage kein Zugriffs-Token enthalten ist (was bedeutet, dass die Anmeldeinformationen ungültig sind), wird der*die Nutzer:in aufgefordert, seine*ihre Daten erneut einzugeben, und die Konversation wird auf den ID state zurückgesetzt. 
  • Wenn die API keine Antwort liefert, wird eine Ausnahme ausgelöst und die Konversation beendet. 
  • Wenn die Authentifizierung erfolgreich ist und ein Zugriffs-Token abgerufen wird, wird der*die Nutzer:in benachrichtigt und die Konversation beendet.

Was denkst Du, was die cancel Funktion bewirkt? Hinweis: Die Logik ist ähnlich wie die Ausnahme, die in der vorherigen Funktion ausgelöst wurde.

Einbindung von lemon.markets 🍋

Es ist an der Zeit, sich mit der Token()-Klasse zu befassen, die unser lemon.markets-Konto auf magische Weise zu authentifizieren scheint. In Wahrheit bist Du der Zauberer und Deine Magie sieht wie folgt aus:

1import os
2import requests
3class Token():
4    def __init__(self):
5        self.auth_url: str = os.environ.get("AUTH_URL")
6    
7    def get_token(self, endpoint: str, data):
8        response = requests.post(self.auth_url + endpoint, data)
9        return response.json()    
10    def authenticate(self, client_id: str, client_secret: str):
11        token_details = {
12            "client_id": client_id,
13            "client_secret": client_secret,
14            "grant_type": "client_credentials",
15        }
16        endpoint = f'oauth2/token/'
17        response = self.get_token(endpoint, token_details)
18        return response

Die Klasse Token() wird mit einer Authentifizierungs-URL initialisiert, die wir in einer .env-Datei definiert haben:

1AUTH_URL=<authorisation URL here>
2BOT_TOKEN=<bot token here>

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

Unsere beiden Funktionen get_token() und authenticate() arbeiten zusammen, um die lemon.markets API-Antwort im JSON-Format zu senden, wenn wir den Authentifizierungs-Endpunkt aufrufen. 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.ID:[MessageHandler(Filters.text & ~Filters.regex('^/'),
29                                          TradingBot().get_client_id)],
30            TradingBot.SECRET:[MessageHandler(Filters.text & ~Filters.regex('^/'),
31                                              TradingBot().get_client_secret)],
32        },
33        # if user currently in conversation but state has no handler or 
34        # handle inappropriate for update
35        fallbacks=[CommandHandler('cancel', TradingBot().cancel)],
36    )
37    dispatcher.add_handler(conv_handler)
38    # Start the Bot
39    updater.start_polling()
40    # Run the Bot until you press Ctrl-C
41    updater.idle()
42if __name__ == '__main__':
43    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 Dein lemon.markets-Konto authentifiziert. 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 die Authentifizierung hinausgeht. In unserem eigenen Trading-Bot haben wir den Authentifizierungsschritt sogar entfernt, damit wirklich jede*r (auch ohne lemon.markets-Konto) ihn ausprobieren kann. 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

Mit OpenFIGI und lemon.markets ein Tickersymbol einer ISIN zuordnen

blog photo

Wenn Du an verschiedenen Börsen tradest, wirst Du feststellen, dass diese meist unterschiedliche Methoden haben um Wertpapiere zu identifizieren. Die US-Börsen verwenden beispielsweise häufig Ticker, während die deutschen Börsen auf eine ISIN verweisen. Und manchmal ist das Wechseln zwischen diesen Identifiern nicht so einfach, wie man es erwarten würde. Stattdessen kannst Du den Prozess automatisieren, indem Du einen (weniger als 10 Zeilen) Code schreibst, der die "Übersetzung" für Dich übernimmt. Lies weiter, um zu erfahren, wie Du die OpenFIGI- und lemon.markets-APIs nutzen kannst, um einem Ticker die entsprechenden ISIN zuzuordnen.

10 Fehler beim (automatisierten) Trading und wie Du sie vermeidest

blog photo

Hallo! Mein Name ist Joanne und ich bin Teil des lemon.markets-Teams in Berlin. Wir bauen eine Trading API, mit der Entwickler:innen ihre eigene Trading Experience gestalten können. Außerdem wollen wir eine Umgebung schaffen, in der Nutzer:innen sich zu der Schnittmenge von  Algorithmus-Entwicklung und dem Aktienmarkt austauschen können. Wir wollen zu dieser Diskussion auch etwas beitragen und haben deshalb eine Liste mit zehn Fehlern zusammengestellt, die Anfänger im automatisierten Handel (um ehrlich zu sein, manchmal auch erfahrene Profis) machen können. Damit sie Dir nicht passieren, haben wir zusätzlich erklärt, wie Du sie vermeiden kannst.

Integration von lemon.markets in Deinen Telegram-Bot (Teil 2 von 2)

blog photo

Hallo! Ich bin Joanne und gehöre zum Team von lemon.markets. Wir sind ein Berliner Start-up, das automatisiertes Trading über APIs ermöglicht. Unser Ziel ist es, Entwicklern und Entwicklerinnen alle Werkzeuge zur Verfügung zu stellen, mit denen sie ihre eigene Trading Experience an der Börse bauen können. Dabei gibt es hunderte Use-Cases für unser Produkt, wie beispielsweise ein eigenes Frontend zum Platzieren von Orders. Dafür kannst Du bei Null anfangen oder einen bereits existierenden Dienst wie beispielsweise Telegram nutzen. In diesem Artikel erweitere ich das Projekt, das wir in unserem letzten Artikel vorgestellt haben.

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.

Products
Pricing
For Developers
SlackGithubBlog
© lemon.markets 2021Privacy PolicyImprint
All systems normal