David Lengweiler
Programming Tutorials and Guides
tutorial GUI python - 8 February 2021
GUI in Python with Kivy
Building a stock market monitor with Python + Kivy
TLDR: Python is extremely powerful and can be used for a multitude of different programs or scripts. But the choice for the correct GUI library can be rather tricky, especially for complex applications. In this tutorial, I am going to show you Kivy and I show you how to build a minimal stock visualizer with it.
Motivation
To visualize a script of program can be somewhat challenging for a lot of programmers. But it can give you and all the people using your tool a lot of comfort and reduce the complexity. While writing a use-once-and-forget GUI(graphical user interface) is a rather easy process in Python, because of libraries like tkinter as an example. But those can become really cluttered when building bigger application.
To solve this problem, more sophisticated GUI libraries/frameworks tend to introduce their own syntax or even semantic to separated the graphical representation from the logic of the program.
One of these libraries is kivy, which besides QT, is one of the main contenders when building complex graphical applictions in Python.
While one still can write the whole code in Python directly, kivy also has the ability to define the visual structure in a seperate file and format which is called Kv language. This language can be compared to HTML, which was one of the main aspects why it sparked my initial interest. As I generally like the way web applications group their code into three different parts.
Build App
For this introduction to kivy we are goint to build a rather simple stock monitor program. This program should consist of a display with shows the values of selected stocks and have the ability to swipe to single displays for each of stocks. But before we start on creating the GUI, which presents all this to the user, we first have build the logic of the program, aka the stocks retrieving engine. Which we are going to do now.
Stocks Tracker - Yahoo Finance
Because fo the popularity of Python you can find for most use cases already written libraries online.
To reduce the complexity of this tutorial we are going to rely on such a library for our stock tracker.
The library, which we are going to use for this tutorial is called yfinance.
yfinance
uses the free API of Yahoo finance to retrieve available prices for stocks online.
To get it installed on your system, you first need to have Python available. Python can be installed by following link:
After installing Python you should be able to access it by entering following in your terminal:
python
# or
py
Now you are ready to install yfinance
.
For this enter following:
pip install yfinance
After finishing the installation you should be ready to go.
The needed logic for our stocks tracker is extremely simple:
import yfinance as yf
def get_current_price(symbol):
ticker = yf.Ticker(symbol)
todays_data = ticker.history(period='1d')
return todays_data['Close'][0]
print(get_current_price('TSLA'))
Graphical User Interface - Kivy
After finishing the tracker logic for our application, we can now start with introducing kivy and working on the GUI. As I already mentioned in the introduction, kivy has mainly two ways of the designing a GUI. You either design it completely inside your python script by initialising Labels, Buttons, etc. programmatically, which for example looks like this:
import kivy
kivy.require('2.0.0')
from kivy.app import App
from kivy.uix.label import Label
class MyApp(App):
def build(self):
return Label(text='Hello world')
if __name__ == '__main__':
MyApp().run()
This can be handy if one only wants to design a simple interface, with no desire to become more complex later on.
Or you could separate the visual part, aka the structure and the looks of your program from your Python code.
This can be done by defining .kv
files and using the kivy specific kv language.
This has the advantage that you do not overcrowd your python code with unnecessary GUI code when not necessary.
But to work with kv
files you have to "tell" Kivy where they can be found.
So let us start by defining a main.py
file with following code:
from kivy.app import App
from kivy.uix.widget import Widget
class StocksWidget(Widget):
def __init__(self, **kwargs):
super(StocksWidget, self).__init__(**kwargs)
class StocksApp(App):
def build(self):
return StocksWidget()
if __name__ == '__main__':
StocksApp().run()
This code has two main parts, for one:
class StocksApp(App):
def build(self):
return StocksWidget()
if __name__ == '__main__':
StocksApp().run()
This part defines a new StocksApp
which inherits from the kivy class App
and defines the main window of our GUI.
Additionally, the StocksApp
generates a StockWidget
, which is defines in the second part:
class StocksWidget(Widget):
def __init__(self, **kwargs):
super(StocksWidget, self).__init__(**kwargs)
This is rather straight forward, as we do not need much functionality here, beside the class itself. The constructor would strictly speaking also not be necessary, but we need it later one, and because of this we already define it here.
Now for the already teased kv
file, for this we are creating a new file called stocks.kv
with the following content.
<StocksWidget>:
GridLayout:
rows: 2
size: root.size
Label:
id: label1
text: root.stocks
font_size: 40
GridLayout:
size_hint_y: 0.1
cols: 3
Button:
id: button1
text: "Backward"
on_release: root.change_view(-1)
Button:
id: button2
text: "Toggle View"
on_release: root.change_view()
Button:
id: button1
text: "Forward"
on_release: root.change_view(1)
For our stock application we have to define a new custom component, with the previously defined name StocksWidget
.
Then we split the view into two rows, by using a GridLayout
with rows: 2
.
The first row we fill with a Label
and the second one with another Gridview
,which in contrast to the previous one uses cols:3
to define three equally sized columns.
These columns we then fill with a button each, where we define different text
s and on_release
functions with arguments.
Bringing all Together
Now we have to add some additional logic to our Python code, which updates our stock label. We also have to define the functions, which are called when pressing our buttons.
class StocksWidget(Widget):
stocks = StringProperty("")
i = 0
showAll = True
def set_stocks(self, dt=None):
temp = []
for stock in STOCK_NAMES:
temp.append(self.get_stock(stock))
if self.showAll:
self.stocks = "\\n".join(temp)
else:
self.stocks = temp[self.i]
def get_stock(self, stock_name):
return "{}: {:.4f}\$".format(stock_name, get_current_price(stock_name))
Kivy uses so-called properties, which can be connected with the GUI.
For our application, we only need one StringProperty
, which we define as stocks
.
When we now reassign stocks
in the set_stocks
function the set value will dynamically change in the GUI.
We also extend our initial constructor with a method to set and update the stock values periodically. For this we replace our initial code with the following:
def __init__(self, **kwargs):
super(StocksWidget, self).__init__(**kwargs)
self.set_stocks()
Clock.schedule_interval(self.set_stocks, 60)
In this new constructor we initial call our set_stocks
function, then we use the kivy provided Clock
to define this function:
Clock.schedule_interval(self.set_stocks, 60)
This calls the provided function every 60 seconds, and update our stock tracker to the correct values. The application can now be started by running following:
py main.py
# or
python main.py
Full Code
main.py
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.widget import Widget
from kivy.properties import StringProperty, ListProperty
import random
import yfinance as yf
STOCK_NAMES = ["TSLA", "GME"]
def get_current_price(symbol):
ticker = yf.Ticker(symbol)
todays_data = ticker.history(period='1d')
return todays_data['Close'][0]
class StocksWidget(Widget):
stocks = StringProperty("")
i = 0
showAll = True
def set_stocks(self, dt=None):
temp = []
for stock in STOCK_NAMES:
temp.append(self.get_stock(stock))
if self.showAll:
self.stocks = "\n".join(temp)
else:
self.stocks = temp[self.i]
def get_stock(self, stock_name):
return "{}: {:.4f}$".format(stock_name, get_current_price(stock_name))
def __init__(self, **kwargs):
super(StocksWidget, self).__init__(**kwargs)
self.set_stocks()
Clock.schedule_interval(self.set_stocks, 60)
def change_view(self, factor=None):
if factor == None:
self.showAll = not self.showAll
else:
self.i = (self.i + factor) % len(STOCK_NAMES)
self.set_stocks()
class StocksApp(App):
def build(self):
return StocksWidget()
if __name__ == '__main__':
StocksApp().run()
stock.kv
<StocksWidget>:
#BoxLayout:
GridLayout:
rows: 2
size: root.size
Label:
id: label1
text: root.stocks
font_size: 40
GridLayout:
size_hint_y: 0.1
cols: 3
Button:
id: button1
text: "Backward"
on_release: root.change_view(-1)
Button:
id: button2
text: "Toggle View"
on_release: root.change_view()
Button:
id: button1
text: "Forward"
on_release: root.change_view(1)