Python GUI mit Kivy – Aktiendaten API anbinden (yfinance)

In dem Artikel bekommt unsere App endlich echte Daten!
Wir werden die Aktiendaten API yfinance anbinden und so die Daten für die eingegebenen Aktien Ticker Symbole abfragen.

Bisher kann unsere App den Benutzer begrüßen und ihn auf eine Aktienübersicht weiterleiten.
Dort können Aktien Ticker Symbole eingegeben und somit zu einer Liste hinzugefügt werden.
Aktuell sind die zugefügten Daten noch hard coded Dummy Daten.
Genau das soll sich jetzt ändern!

Hier hast du eine Übersicht über die bisherigen Artikel.
Wenn du direkt loslegen willst, kannst du natürlich auch hier wieder den Code herunterladen.

Unsicher mit Git?
Lade jetzt dein Git CheatSheet herunter!

Kivy Serie

Unser Ziel heute ist keine visuelle Änderung.
Wir wollen lediglich die Dummy Daten durch echte Daten aus yfinance ersetzen.

Daten aus yfinance beziehen

Unsere Funktion add_ticker_symbol beinhaltet im Moment nur das Dummy Dictionary und den Aufruf an add_ticker_row.
Nachdem du yfinance installiert und importiert hast, kannst du durch die Übergabe des Aktien Ticker Symbols ganz einfach die Daten der Aktie bekommen.
Wenn du mehr zu yfinance wissen willst, hier habe ich schon einen Artikel dazu geschrieben.
Das Symbol nehmen wir einfach aus dem ticker_input Label Feld, in das der Benutzer sein Symbol eingibt.
Mit .strip() werden dabei Leerzeichen weggenommen, die ggf. versehentlich am Anfang oder Ende eingegben wurden.

import yfinance

def add_ticker_symbol(self, *args):
    symbol = self.ticker_input.text.strip()
    stock = yfinance.Ticker(ticker=symbol)
    data = stock.info
    self.add_ticker_row(data['symbol'], data['longName'], str(data['regularMarketPrice']))

Über stock.info bekommen wir jetzt die Daten und können dort Dinge wie symbol, longName und regularMarketPrice entnehmen.
Der regularMarketPrice muss noch in ein str() geworfen werden, da es sich um eine Zahl handelt. Das Label, in das wir den Preis in add_ticker_row legen, nimmt nur Text entgegen.

yfinance Roh-Daten eingefügt
yfinance Roh-Daten eingefügt

Nicht schön, aber immerhin haben wir die Daten drin!
Beim Aufruf fallen aber ein paar Dinge auf:

  • Die App friert ein, bis yfinance antwortet.
    • Schön wäre eine Ausgabe an den Benutzer, dass gerade Daten abgefragt werden.
    • Noch schöner wäre, wenn die App dabei nicht hängt. Die Abfrage an yfinance also im Hintergrund passiert.
  • Der Preis hat weder eine Währung, noch sieht er schön aus.
    • Wir brauchen die Währung.
    • Der Preis muss formatiert werden.
  • Eine Falscheingabe führt zum Absturz
    • MSFT.DE statt MSF.DE und schon passiert einfach nichts mehr.
    • Hier muss eine Fehlerbehandlung her

Die App friert ein, bis yfinance antwortet

Ein Programm, das nicht antwortet, ist immer schlecht.
Auch wenn es nur für einen kurzen Moment ist, ruft es doch ein ungutes Gefühl beim Benutzer hervor.
Wir wollen also unbedingt vermeiden, dass der Benutzer einfach ohne irgendeine Information eine hängende App sieht.

Meldung an den Benutzer

Der einfachste und schnellste Weg dafür ist eine Meldung an den Benutzer auszugeben.
Dafür fügen wir einfach noch schnell ein Label vor unserem ScrollView ein.

class StockView(GridLayout):
    def __init__(self, **kwargs):
        ....
        self.message = Label(size_hint=(1, 0.1))
        self.add_widget(self.message)

        self.stock_view = ScrollView(size_hint=(1, None),
                                     size=(Window.width, Window.height * 0.8),
                                     do_scroll_x=False,
                                     do_scroll_y=True)
        ...

Damit haben wir jetzt ein Feld mit 10 % Höhe, in dem wir Nachrichten anzeigen können.
Um Platz dafür zu schaffen, habe ich den ScrollView einfach um die 10 % verkürzt.

Jetzt können wir unsere add_ticker_symbol Funktion einfach damit beginnen lassen, dass wir eine Nachricht an den Benutzer ausgeben.
Dann wird die zugefügt, bevor die yfinance API aufgerufen wird.

def add_ticker_symbol(self, *args):
    self.message.text = 'Bitte warten, während die Daten abgefragt werden...'
    symbol = self.ticker_input.text.strip()
    stock = yfinance.Ticker(ticker=symbol)
    data = stock.info
    self.add_ticker_row(data['symbol'], data['longName'], str(data['regularMarketPrice']))

Hmm…
Das war noch nicht des Rätsels Lösung.
Jetzt wird zwar eine Meldung ausgegeben, die App friert aber trotzdem noch ein.
Und erst, wenn die Daten angekommen sind, erscheint auch die Meldung.
Das war ja wohl nicht Sinn der Sache.
Wir müssen also die Datenabfrage noch irgendwie in den Hintergrund verschieben.

Anfrage in den Hintergrund verschieben

Wenn etwas im “Hintergrund” ausgeführt werden soll, gibt es in der Regel zwei Möglichkeiten:
Prozesse oder Threads.
Um es kurz zu machen:
Willst du wirklich parallel arbeiten, brauchst du einen extra Prozess.
Reicht es länger wartende Dinge in den Hintergrund zu verschieben, kannst du mit Threads arbeiten.
In einem Prozess laufen mehrere Threads.

Also, was brauchen wir hier?
Nun die Abfrage an yfinance wird rausgeschickt und dann warten wir eigentlich nur auf die Antwort.
Wir müssen also nicht großartig weitere Prozesse erstellen, ein einfacher, kleiner Thread reicht uns schon aus.

Um Code in einem Thread auszuführen, muss er einfach nur in eine extra Funktion ausgelagert werden.
Lass uns also aus der Funktion add_ticker_symbol die drei Zeilen zur Datenabfrage und die eine Zeile zum Einfügen der Daten in die GUI auslagern.

def query_stock_data(self):
    symbol = self.ticker_input.text.strip()
    stock = yfinance.Ticker(ticker=symbol)
    data = stock.info
    self.add_ticker_row(data['symbol'], data['longName'], str(data['regularMarketPrice']))

Als Nächstes importieren wir die Klasse Thread aus dem Modul threading.
Das musst du nicht installieren. Es ist bereits Teil der Standard Installation.
Anschließend kannst du in add_ticker_symbol einen neuen Thread erzeugen.
Dafür übergibst du an die Klasse Thread einfach direkt die Referenz auf die Funktion, die im Thread laufen soll.
Und abschließend musst du den so erzeugten Thread nur noch mit .start() starten.

# Import gehört natürlich nach oben in die Datei
from threading import Thread

def add_ticker_symbol(self, *args):
    self.message.text = 'Bitte warten, während die Daten abgefragt werden...'
    query_data_thread = Thread(target=self.query_stock_data)
    query_data_thread.start()

Das ist auch schon alles!
Wenn du dein Programm jetzt nochmal laufen lässt, erscheint sofort nach dem Absenden des Aktien Ticker Symbols die Meldung.
Sobald die Daten angekommen sind, werden sie in der Liste angezeigt.
Während der ganzen Zeit bleibt die App weiter benutzbar.
Es könnten also sogar noch weitere Ticker Symbole eingetragen und abgeschickt werden.

yfinance blockiert die Kivy App nicht mehr.
yfinance blockiert die Kivy App nicht mehr.

Was direkt auffällt ist, dass die Meldung auch dann noch weiter angezeigt wird, wenn die Abfrage bereits abgearbeitet ist.
Das können wir auch direkt beheben.
Dafür müssen wir, nachdem wir die Daten erhalten haben, einfach die Nachricht an den Benutzer entfernen, bzw. auf leer setzen.

Wenn die Daten erfolgreich angekommen sind, wird von unserem Thread die Funktion add_ticker_row aufgerufen.
Sobald wir da ankommen, können wir also sicher sein, dass die Daten vorhanden sind.
Die Meldung kann dort also ausgeblendet und die Zeile befüllt werden.

def add_ticker_row(self, ticker, name, price):
    self.message.text = ''
    ....

Der Preis muss formatiert werden

Als Nächstes fällt auf, dass der Preis mit einem Punkt (.) und nur einer Nachkommastelle formatiert ist.
Außerdem fehlt auch die Währung.
Die Währung bekommen wir auch aus yfinance. Deshalb kümmern wir uns zuerst einmal darum.

Währung an den Preis anfügen

Aktuell nehmen wir den Preis direkt aus der Antwort von yfinance, indem wir auf regularMarketPrice zugreifen.
Zusätzlich gibt es in den Daten auch noch ein Feld currency, in dem die Währung hinterlegt ist.

Wir können also sowohl den Preis, als auch die Währung aus den Daten entnehmen und über einen formatierten String (f-Strings) gemeinsam darstellen.
Dafür bauen wir uns erst einen f-String zusammen und übergeben dann anstelle des Preises direkt den formatierten String an die add_ticker_row Funktion.

def query_stock_data(self):
    symbol = self.ticker_input.text.strip()
    stock = yfinance.Ticker(ticker=symbol)
    data = stock.info
    price = f'{data.get("regularMarketPrice")} {data.get("currency")}'
    self.add_ticker_row(data['symbol'], data['longName'], price)
Aktienpreis von yfinance mit Währung.
Aktienpreis von yfinance mit Währung.

Wunderbar.
Jetzt geht es zumindest nicht mehr um Äpfel und Birnen.
Es ist auf den ersten Blick klar, um welche Währung es sich handelt.
Dann kommt als Nächstes der Preis.

Preis formatieren

Den Preis bekommen wir ja schon und entnehmen ihn auch aus den yfinance Daten.
Jetzt geht es darum den Preis, so wie es sich gehört, mit zwei Nachkommastellen anzuzeigen.
Und das unabhängig davon, was nach dem Komma steht.

In der verlinkten Anleitung ist auch schon zu sehen, wie einfach so eine Formatierung mit f-Strings funktioniert.
In einem f-String werden Variablen in geschweiften Klammern eingefügt.
Hinter diese Variable kann, durch einen Doppelpunkt getrennt, eine Formatierungsregel angegeben werden.
In unserem Fall soll eine Gleitkommazahl formatiert werden.
Also kommt schon mal das f für float ans Ende der Regel.

Zusätzlich wollen wir zwei Zahlen nach dem Komma.
Damit ist unsere volle Regel: .2f
Also nach dem Komma → .
zwei Stellen → .2
Für eine Gleitkommazahl → .2f

price = f'{data.get("regularMarketPrice"):.2f} {data.get("currency")}'
Aktienpreis mit zwei Nachkommastellen und Währung formatiert.
Aktienpreis mit zwei Nachkommastellen und Währung formatiert.

Fehlerbehandlung bei Falscheingaben

Wenn du beim Herumprobieren zum Beispiel mal die MSFT.DE statt MSF.DE eingegeben hast, dürfte dir aufgefallen sein, dass es zu einem Fehler kommt.
Für das Aktien Ticker Symbol gibt es so in yfinance keinen Preis.
Damit kommt es zu einem Fehler und das Programm fällt ohne Meldung an den Benutzer auf die Nase.
Er weiß einfach nicht, was passiert ist.

Das soll natürlich nicht passieren.
Eine ganz simple Fehlerbehandlung ist deshalb zu überprüfen, ob es für das Symbol denn überhaupt einen Preis gibt.
Gibt es einen Preis, können wir die Logik laufen lassen, wie gerade gebaut.
Gibt es keinen Preis, wird einfach keine Zeile hinzugefügt und der Benutzer bekommt eine Meldung, dass für das angegebene Symbol kein Ergebnis gefunden werden konnte.

Damit weiß der Benutzer, dass etwas passiert ist, was passiert ist und was er ggf. ändern muss, damit alles wieder funktioniert.

Zugegeben, die Prüfung ist sehr rudimentär.
Aber sie erfüllt erstmal ihren Zweck.
Erreichen können wir das über eine einfache if-Bedingung.

def query_stock_data(self):
    symbol = self.ticker_input.text.strip()
    stock = yfinance.Ticker(ticker=symbol)
    data = stock.info
    if 'regularMarketPrice' in data and data.get('regularMarketPrice') is not None:
        price = f'{data.get("regularMarketPrice"):.2f} {data.get("currency")}'
        self.add_ticker_row(data.get('symbol'), data.get('longName'), price)
    else:
        self.message.text = f'Für das angegebene Symbol {symbol} gab es kein Ergebnis.'

Schon werden ungültige Aktien Ticker Symbole abgefangen, der Benutzer bekommt eine Meldung und alles läuft ein wenig mehr rund.

Zusammenfassung

In dem Artikel haben wir endlich unsere Dummy Daten durch echte Daten aus einer API ersetzt.
Dafür haben wir mit der Benutzereingabe die API von yfinance abgefragt.

Nachdem die App dadurch während der Abfragezeit eingefroren ist, kamen direkt Threads zum Einsatz, um die Ausführung in den Hintergrund zu verschieben.

Abschließens gab es noch ein paar Schönheitsoperationen.
Wir haben den Preis formatiert und die ersten groben Fehler abgefangen.

Damit ist die Kivy App jetzt endlich in einem benutzbaren Zustand!
Die Kernfunktionalität steht.

In den nächsten Beiträgen werden wir uns darum kümmern, die Einträge auch über die Laufzeit hinaus zu speichern.
Und natürlich müssen wir das Programm auch irgendwann ausliefern können.
Also bleib unbedingt dran und wenn du nichts verpassen willst, komm direkt ins Discord und schließ dich der Gruppe an!

Wie kannst du am schnellsten Summen... x
Wie kannst du am schnellsten Summen bilden? Python Built-Ins | #Shorts

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht.

Scroll to Top
ANFORDERN
Anfordern
Bereit zum Ausdrucken | Mit Erklärungen
IMMER ZUR HAND
ANFORDERN
Anfordern
Bereit zum Ausdrucken | Mit Erklärungen
IMMER ZUR HAND
Trag dich schnell ein und bekomme Zugang zum Download!
Gib mir den Download!