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!
Wenn du direkt loslegen willst, kannst du natürlich auch hier wieder den Code herunterladen.
Kivy Serie
- Erste Schritte mit Fenstern
- Den Benutzer Willkommen heißen
- Die Verpackung zählt!
- Weitere Ansichten (Views)
- Scrollbare Liste mit Aktiendaten
- Aktiendaten API anbinden (yfinance)
- Fehler “Instruction outside the main Kivy thread”
- Element aus dem Layout entfernen
- Aktien in der Watchlist speichern
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.
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.
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)
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")}'
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!
Ingo Janssen ist ein Softwareentwickler mit über 10 Jahren Erfahrung in der Leitung seines eigenen Unternehmens.
Er studierte Wirtschaftsinformatik an der TH Deggendorf und hat Softwareentwicklung an der FOM Hochschule in München unterrichtet.
Ingo hat mit einer Vielzahl von Unternehmen zusammengearbeitet, von kleinen und mittelständischen Unternehmen bis hin zu MDAX- und DAX-gelisteten Unternehmen.
Ingo ist leidenschaftlich daran interessiert, sein Wissen und seine Expertise mit anderen zu teilen. Aus diesem Grund betreibt er einen YouTube-Kanal mit Programmier-Tutorials und eine Discord-Community, in der Entwickler miteinander in Kontakt treten und voneinander lernen können.
Sie können Ingo auch auf LinkedIn, Xing und Gulp finden, wo er Updates über seine Arbeit teilt und Einblicke in die Tech-Branche gibt.
YouTube | Discord | LinkedIn | Xing | Gulp Profile