1

I am trying to make a standalone GUI application that displays real time price data from a stock price API. Due to the API, it is much easier to make this program Python based.

The stock data from a domain of stocks is streamed from an API and stored at a set interval (atm around 500ms).

My aim is to insert the price information for the stock, when that stock makes a new high or a new low. Additionally the rows will need to be coloured different colours based on the price (red/blue)

E.g:

Time     | Ticker | Price 
11:00:00 | ABCD   | 109.50
11:00:50 | WXYZ   | 123.30
11:01:00 | ABCD   | 110.01
11:01:50 | EFGH   | 50.38

As you can imagine, very quickly this table will fill up, and it will be running all day during trading hours, so will need to keep up with constant additions.

At the moment, I've been looking at using Tkinter, but I am inexperienced with GUI related programming, so I'm not sure if it is capable of achieving what I would like. I have made a start with dummy data, and a worst case scenario (adding every 100ms). It already seems fairly sluggish, so I'm not sure if this is the way to go, particularly with the scrollbar workaround required (Adding a scrollbar to a group of widgets in Tkinter) :

import tkinter as tk
import tkinter.ttk as ttk


class StockDisplay(tk.Frame):
    def __init__(self):
        tk.Frame.__init__(self)
        self.main_frame = tk.Frame()
        self.main_frame.bind("<Configure>", lambda e: self.my_canvas.configure(scrollregion=self.my_canvas.bbox("all")))
        self.main_frame.pack(fill=tk.BOTH, expand=1)

        self.my_canvas = tk.Canvas(self.main_frame)
        self.my_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)

        self.my_scrollbar = ttk.Scrollbar(self.main_frame, orient=tk.VERTICAL, command=self.my_canvas.yview)
        self.my_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.my_canvas.configure(yscrollcommand=self.my_scrollbar.set)

        self.second_frame = tk.Frame(self.my_canvas)

        self.my_canvas.create_window((0, 0), window=self.second_frame, anchor="nw")

        self.height = 0
        self.init_table()
        self.add_row()

    def init_table(self):
        tk.Label(self.second_frame, text="Time",).grid(row=self.height, column=0, padx=10)
        tk.Label(self.second_frame, text="Ticker").grid(row=self.height, column=1, padx=10)
        tk.Label(self.second_frame, text="Price").grid(row=self.height, column=2, padx=10)

    def add_row(self):
        self.height += 1
        tk.Label(self.second_frame, text="Time").grid(row=self.height, column=0, padx=10)
        tk.Label(self.second_frame, text="Ticker " + str(self.height)).grid(row=self.height, column=1, padx=10)
        tk.Label(self.second_frame, text="Price").grid(row=self.height, column=2, padx=10)
        self.reset_scrollregion()
        self.after(100, self.add_row)

    def reset_scrollregion(self):
        self.my_canvas.configure(scrollregion=self.my_canvas.bbox("all"))


if __name__ == '__main__':
    root = tk.Tk()
    root.title('Tkicker')
    root.geometry("500x400")
    display = StockDisplay()
    root.mainloop()

My questions are:

1.) Is Tkinter going to be able to acheive my goal?

a.) If yes: Is there a good/better way to allow this updating table (i/e is the grid method the best way)?

b.) If no: Is there a better GUI application that I could use to achieve this?

Any advice is very much appreciated.

Thanks!

ACWalker
  • 11
  • 2
  • 2
    Is there a reason you're using labels rather than a text widget or canvas? Can you do some math to tell us exactly how many rows and columns you expect to have at the end of a day? thousands? Hundreds of thousands? – Bryan Oakley Jan 28 '21 at 14:45
  • Also, are you wanting to _add_ rows ten times a second, or will you have just a few rows that you want to _update_ ten times a second? – Bryan Oakley Jan 28 '21 at 15:20
  • @BryanOakley thank you for taking the time to reply For the first clarification, labels are just a placeholder, any widget that displays text and allows for font colour and background colour will be fine. For a big upper bound, lets say 100 stocks, and they make a new day high/low 10 times a minute, so we would have 100 stocks * (60/10) times a minute * 60 mins in an hour * 8 hours = 288,000. However, as you can imagine the day low and highs are primarily made in the first few mins of the day, and over the course of the day it will reduce. I would need to ADD these rows, and not update. – ACWalker Jan 28 '21 at 16:11

1 Answers1

1

The only way to display hundreds of thousands of strings would be with the text widget. I'm not sure what the upper limit is, but I tested it with 200,000 lines and it seemed to work ok. You definitely can't create 200,000 label widgets and expect tkinter to perform in a usable way. The canvas also has limitations on the number of items it can manage.

You can configure tabstops in the text widget so that your data aligns in columns. You can use tags to add color to a line or parts of a line.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • Thank you very much, this has been a great help. I just need to get my head around tab stops and I think this may be the solution! – ACWalker Jan 28 '21 at 18:12
  • I'm using tags to add borders to each line of text to further "simulate" a table look. However consecutive tags with the same borderstyle and width seem to join up, even with different tag names: `self.output.tag_configure("1", background="lightyellow", borderwidth=1, relief="solid") self.output.tag_configure("2", background="lightyellow", borderwidth=1, relief="solid") self.output.tag_configure("3", background="lightblue", borderwidth=1, relief="solid") self.output.tag_configure("4", background="lightblue", borderwidth=1, relief="solid")` – ACWalker Jan 29 '21 at 08:24
  • I'm adding them randomly at the moment like this: `self.output.tag_add(str((self.count % 4) + 1), str(float(self.count+1)), str(float(self.count + 2)))` Where count is the number of stock prices currently added. If I change the colour or borderwidth, they are consecutively added, but it seems to merge similar tags, I'm not sure if there is a way around this. – ACWalker Jan 29 '21 at 08:28
  • 1
    @ACWalker: that's a completely different question, and there's no way I can answer it in the comment section. But yes, consecutive characters will the same tag will "join up" as you put it. – Bryan Oakley Jan 29 '21 at 14:05
  • My bad, I will ask another question to see if there is a better way to get around this. – ACWalker Jan 29 '21 at 15:34