Tkinter SQLite Stock Tracker and Data Viewer

This project extends our Tkinter real-time data tracker by adding persistent storage with SQLite.

The project has two parts.

  1. Fetch the latest available stock price, save each observation in SQLite and update a Matplotlib graph.
  2. Open the stored database records in a second Tkinter application and display them using a Treeview.

The worker thread performs only the external data request. Results are sent through a Queue so database writes, StringVar updates and Matplotlib drawing remain on the Tkinter main thread.

Stock data note: The example displays the latest available intraday value returned by the data source. It should not be treated as guaranteed exchange-level real-time data.
External data
      |
      v
Worker thread
      |
      v
Queue
      |
      v
Tkinter main thread
      |
      +--> SQLite
      |
      +--> Matplotlib
      |
      +--> Labels

SQLite
  |
  v
Combobox
  |
  v
Treeview viewer

Tkinter stock tracker storing stock price data in SQLite database

Real-Time Stock Data Viewer with SQLite and Tkinter

Part I: Track and Store Stock Data Top ↑

The first application combines Tkinter, threading, SQLite and Matplotlib.

The program lets the user choose one of several Indian stocks or market indices from a Combobox.

After tracking starts, Python periodically:

  1. fetches the latest available value;
  2. sends it from the worker thread to the Tkinter main thread;
  3. stores it in SQLite;
  4. adds it to the graph;
  5. schedules the next request.

Libraries Used Top ↑

import queue
import sqlite3
import threading
import tkinter as tk

from tkinter import ttk

import yfinance as yf

from matplotlib.figure import Figure

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

The main roles are:

  • Tkinter: desktop interface.
  • threading: runs the network request without blocking the GUI.
  • Queue: passes the worker result to the Tkinter main thread.
  • SQLite: stores historical observations.
  • Matplotlib: plots the collected values.

Create the SQLite Database Top ↑

The database file is stored in the same directory as the Python program.

db_path=os.path.join(
    os.path.dirname(
        os.path.abspath(__file__)
    ),
    'stock_data.db'
)

The table keeps the same columns as the earlier version of this project so an existing database remains compatible.

def create_database():

    with sqlite3.connect(db_path) as conn:

        conn.execute(
            '''CREATE TABLE IF NOT EXISTS stock_prices (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                stock_name TEXT NOT NULL,
                price REAL NOT NULL,
                timestamp TEXT NOT NULL
            )'''
        )

        conn.execute(
            '''CREATE INDEX IF NOT EXISTS
            idx_stock_prices_name_id
            ON stock_prices(stock_name,id)'''
        )

The index helps when the viewer requests records for one stock ordered by their ID.

Fetch the Latest Available Price Top ↑

The fetching function is independent of Tkinter and SQLite.

def fetch_stock_price(ticker):

    stock=yf.Ticker(ticker)

    data=stock.history(
        period='1d',
        interval='1m'
    )

    if data.empty:

        raise ValueError(
            'No intraday data was returned.'
        )

    close_values=data['Close'].dropna()

    if close_values.empty:

        raise ValueError(
            'No price data is available.'
        )

    price=float(
        close_values.iloc[-1]
    )

    timestamp=close_values.index[-1]

    return price,timestamp

Use a Worker Thread and Queue Top ↑

The worker performs only the potentially slow external request.

def fetch_worker(
    tracker_id,
    ticker,
    stock_name
):

    try:

        price,timestamp=fetch_stock_price(
            ticker
        )

        result_queue.put(
            (
                'ok',
                tracker_id,
                stock_name,
                price,
                timestamp
            )
        )

    except Exception as e:

        result_queue.put(
            (
                'error',
                tracker_id,
                stock_name,
                str(e),
                None
            )
        )

tracker_id identifies the current tracking session. If the user changes stocks before an older request finishes, that older result can be ignored.

The worker thread should not call canvas.draw(), StringVar.set() or other Tkinter methods directly.

Save Stock Data in SQLite Top ↑

When the main Tkinter thread receives a successful worker result, the record is inserted using parameters.

def save_to_database(
    stock_name,
    price,
    timestamp
):

    with sqlite3.connect(db_path) as conn:

        conn.execute(
            '''INSERT INTO stock_prices
            (stock_name,price,timestamp)
            VALUES (?,?,?)''',
            (
                stock_name,
                price,
                timestamp
            )
        )

The timestamp stored is the timestamp associated with the returned market data rather than simply the computer's current clock time.

Update the Matplotlib Graph Top ↑

Matplotlib is embedded in Tkinter using FigureCanvasTkAgg.

The graph is updated only from the Tkinter main thread.

def plot_graph(stock_name):

    ax.clear()

    ax.plot(
        times,
        prices,
        marker='o'
    )

    ax.set_title(
        f'Latest Price Data - {stock_name}'
    )

    ax.set_xlabel(
        'Time'
    )

    ax.set_ylabel(
        'Price / Index Value'
    )

    fig.autofmt_xdate()

    canvas.draw_idle()

The example keeps only the latest 60 plotted points so the in-memory graph does not grow indefinitely.

Start and Stop Tracking Top ↑

Starting another stock does not call thread.join() from the GUI thread.

Instead, the application increments the tracking-session ID and starts a new request.

tracker_id+=1

If an old worker returns later, its ID no longer matches the active session and the result is ignored.

This keeps the GUI responsive even if the previous network request takes time to finish.

Complete Tracker and SQLite Storage Program Top ↑

Why Database and Graph Updates Stay on the Main Thread Top ↑

The older program performed both:

plot_graph(stock_name)
canvas.draw()

inside the worker thread.

The revised design keeps the worker focused on the external request and passes the result back to Tkinter.

Worker:
    fetch data

Main thread:
    save data
    update StringVar
    update graph

This also makes the project easier to extend because fetching, persistence and presentation have separate responsibilities.

Part II: Display Stored Stock Data Top ↑

Tkinter Treeview displaying stored stock price data from SQLite

The second application opens the same stock_data.db file and displays saved observations using a Treeview.

The user chooses a stock from a read-only Combobox.

The viewer then loads the latest stored records for that stock.

There is no need to hardcode the stock list in the viewer. We can ask SQLite which stock names are actually present.

def load_stock_names():

    with sqlite3.connect(db_path) as conn:

        rows=conn.execute(
            '''SELECT DISTINCT stock_name
            FROM stock_prices
            ORDER BY stock_name'''
        ).fetchall()

    stock_names=[
        row[0]
        for row in rows
    ]

    stock_dropdown['values']=stock_names

If no rows exist, the interface displays a message rather than trying to access stock_names[0].

Display Stored Records in Treeview Top ↑

The viewer uses explicit columns:

SELECT
    id,
    stock_name,
    price,
    timestamp
FROM stock_prices
WHERE stock_name=?
ORDER BY id DESC
LIMIT ?

The selected stock and limit are passed as parameters.

rows=conn.execute(
    query,
    (
        selected_stock,
        MAX_ROWS
    )
).fetchall()

The database ID is used as the Treeview iid.

tree.insert(
    '',
    tk.END,
    iid=str(row[0]),
    values=(
        row[1],
        f'{row[2]:.2f}',
        row[3]
    )
)

The example loads a maximum of 500 recent records so an old database with thousands of observations does not immediately insert every row into the GUI.

Complete SQLite Stock Data Viewer Top ↑

How This Version Improves the Original Project Top ↑

GUI Updates Stay on the Main Thread

The worker does not draw Matplotlib graphs or modify Tkinter widgets.

No GUI Thread Waiting with join()

The Start and close callbacks do not wait synchronously for a sleeping worker thread.

One-Minute Source Data Uses a 60-Second Refresh

The application avoids repeatedly requesting one-minute data every five seconds.

Existing SQLite Table Remains Compatible

The original columns remain:

id
stock_name
price
timestamp

This means an existing stock_data.db created by the older tutorial can still be opened by the revised viewer.

The Viewer Does Not Load Unlimited Rows

Only the latest 500 records are loaded by default.

Database Queries Use Parameters

The selected stock is never concatenated into the SQL statement.

Treeview Uses the Database Primary Key

The unique SQLite id becomes the Treeview iid, even though it does not have to be displayed as a visible column.

Next: Dedicated Real-Time Graph Project Top ↑

This page combines fetching, storage, graphing and database viewing. The next tutorial focuses more closely on the visualization side of the project.

Real-Time Stock Graph

Frequently Asked Questions Top ↑

Q1: Why save stock prices in SQLite?

SQLite gives the application persistent local storage so observations can be viewed again after the tracking application is closed.

Q2: Why does the worker thread not update the graph directly?

Tkinter and the embedded Matplotlib canvas belong to the GUI thread. The worker fetches data and sends its result through a Queue.

Q3: Why is each SQLite connection opened inside a function?

Short-lived local connections keep connection ownership clear and avoid sharing one SQLite connection across unrelated operations or threads.

Q4: Is the yfinance value guaranteed to be real-time?

No. It represents the latest available value returned by the source and may be delayed depending on market status and data availability.

Q5: Why does the graph keep only 60 points?

Limiting the in-memory list prevents the graph from growing indefinitely during a long tracking session. All stored observations can still remain in SQLite.

Q6: Can the viewer show stock data already stored by the older program?

Yes. The revised code keeps the original stock_prices table columns for compatibility.

Q7: Why does the viewer limit the result to 500 rows?

A Treeview does not need to load an unlimited database history at once. Limiting the first display keeps the interface responsive. Pagination or date filters can be added later.


Real-Time Data Tracker Real-Time Data Graph Treeview Pagination

More Projects using Tkinter


Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
©2000-2026   plus2net.com   All rights reserved worldwide Privacy Policy Disclaimer