This Python script creates a mobile-friendly graphical application using Tkinter to analyze stock market data for a chosen ticker symbol, such as NVDA.
When you launch the app, it presents a user interface with input fields to type a stock symbol and select a time frame, like one month, six months, or one year. Upon clicking the Load button, the script sends a direct web request to Yahoo Finance. It uses a custom user browser agent header to fetch accurate raw historical market price data, avoiding network blocking or API failures on mobile Python environments.
Once the raw data is downloaded, the script converts the response into a structured Pandas dataframe containing daily open, high, low, close, and volume values. It then automatically computes several key technical market indicators. It calculates 20-day and 50-day Simple Moving Averages to show price trends over time. It computes Bollinger Bands to illustrate price volatility boundaries, and it calculates the Relative Strength Index over 14 days to identify overbought or oversold market conditions.
At the top of the interface, the app displays text cards showing the latest stock price, current RSI value, and moving averages. Below these metrics, it renders two interactive charts using Matplotlib embedded directly into Tkinter. The upper chart plots the stock closing price alongside the moving averages and Bollinger Bands shading. The lower chart plots the RSI line with dashed horizontal benchmark lines at values 30 and 70.
In short, this script serves as a lightweight, real-time stock dashboard that fetches financial market data, calculates essential technical indicators, and visualizes price action clearly on mobile screens.

Let's see the source code:
import tkinter as tk
from tkinter import ttk, messagebox
import pandas as pd
import numpy as np
import requests
from datetime import datetime, timedelta
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class FinancialDashboardApp:
def __init__(self, root):
self.root = root
self.root.title("Market Indicators Dashboard")
self.root.geometry("480x800")
# --- Controls Frame (Top) ---
control_frame = ttk.LabelFrame(root, text=" Parametrii Simbol ")
control_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(control_frame, text="Ticker:").grid(row=0, column=0, padx=5, pady=5)
self.ticker_entry = ttk.Entry(control_frame, width=8)
self.ticker_entry.insert(0, "NVDA")
self.ticker_entry.grid(row=0, column=1, padx=5, pady=5)
ttk.Label(control_frame, text="Perioadă:").grid(row=0, column=2, padx=5, pady=5)
self.period_cb = ttk.Combobox(control_frame, values=["1mo", "3mo", "6mo", "1y"], width=6)
self.period_cb.set("6mo")
self.period_cb.grid(row=0, column=3, padx=5, pady=5)
fetch_btn = ttk.Button(control_frame, text="Încarcă", command=self.load_data)
fetch_btn.grid(row=0, column=4, padx=5, pady=5)
# --- Info Cards Frame (Middle) ---
self.info_frame = ttk.LabelFrame(root, text=" Date & Indicatori ")
self.info_frame.pack(fill="x", padx=10, pady=5)
self.lbl_price = ttk.Label(self.info_frame, text="Preț: -", font=("Helvetica", 10, "bold"))
self.lbl_price.grid(row=0, column=0, sticky="w", padx=10, pady=2)
self.lbl_rsi = ttk.Label(self.info_frame, text="RSI (14): -")
self.lbl_rsi.grid(row=0, column=1, sticky="w", padx=10, pady=2)
self.lbl_sma20 = ttk.Label(self.info_frame, text="SMA 20: -")
self.lbl_sma20.grid(row=1, column=0, sticky="w", padx=10, pady=2)
self.lbl_sma50 = ttk.Label(self.info_frame, text="SMA 50: -")
self.lbl_sma50.grid(row=1, column=1, sticky="w", padx=10, pady=2)
# --- Matplotlib Canvas Frame (Bottom) ---
self.plot_frame = ttk.Frame(root)
self.plot_frame.pack(fill="both", expand=True, padx=10, pady=5)
self.fig = Figure(figsize=(5, 6), dpi=90)
self.ax1 = self.fig.add_subplot(211) # Subplot Preț + SMA / Bollinger
self.ax2 = self.fig.add_subplot(212, sharex=self.ax1) # Subplot RSI
self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_frame)
self.canvas.get_tk_widget().pack(fill="both", expand=True)
self.load_data()
def fetch_yahoo_data(self, symbol, range_str):
"""Descarcă date brute din Yahoo v8 API simulând un browser desktop."""
url = f"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?range={range_str}&interval=1d"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
response = requests.get(url, headers=headers, timeout=10)
if response.status_code != 200:
return pd.DataFrame()
data = response.json()
result = data['chart']['result'][0]
timestamps = result['timestamp']
quote = result['indicators']['quote'][0]
df = pd.DataFrame({
'Open': quote['open'],
'High': quote['high'],
'Low': quote['low'],
'Close': quote['close'],
'Volume': quote['volume']
}, index=pd.to_datetime(timestamps, unit='s'))
df.dropna(subset=['Close'], inplace=True)
return df
def calculate_indicators(self, df):
# 1. Simple Moving Averages (SMA)
df['SMA_20'] = df['Close'].rolling(window=20).mean()
df['SMA_50'] = df['Close'].rolling(window=50).mean()
# 2. Bollinger Bands
std20 = df['Close'].rolling(window=20).std()
df['BB_Upper'] = df['SMA_20'] + (std20 * 2)
df['BB_Lower'] = df['SMA_20'] - (std20 * 2)
# 3. RSI (14 zile)
delta = df['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df['RSI'] = 100 - (100 / (1 + rs))
return df
def load_data(self):
ticker_symbol = self.ticker_entry.get().strip().upper()
period = self.period_cb.get()
if not ticker_symbol:
messagebox.showerror("Eroare", "Introduceți un ticker valid.")
return
try:
# Preluare date prin request HTTP direct
df = self.fetch_yahoo_data(ticker_symbol, period)
if df.empty:
messagebox.showwarning("Atenție", f"Nu s-au găsit date pentru tickerul '{ticker_symbol}'. Verificați conexiunea la internet sau simbolul introduse.")
return
df = self.calculate_indicators(df)
curr_price = df['Close'].iloc[-1]
last_rsi = df['RSI'].iloc[-1] if not pd.isna(df['RSI'].iloc[-1]) else 0.0
last_sma20 = df['SMA_20'].iloc[-1] if not pd.isna(df['SMA_20'].iloc[-1]) else 0.0
last_sma50 = df['SMA_50'].iloc[-1] if not pd.isna(df['SMA_50'].iloc[-1]) else 0.0
# Actualizare etichete UI
self.lbl_price.config(text=f"Preț: {curr_price:.2f} USD")
self.lbl_rsi.config(text=f"RSI (14): {last_rsi:.2f}")
self.lbl_sma20.config(text=f"SMA 20: {last_sma20:.2f}")
self.lbl_sma50.config(text=f"SMA 50: {last_sma50:.2f}")
# Generare grafice
self.plot_charts(df, ticker_symbol)
except Exception as e:
messagebox.showerror("Eroare", f"A apărut o eroare la procesare:\n{e}")
def plot_charts(self, df, ticker_symbol):
self.ax1.clear()
self.ax2.clear()
# Grafic 1: Preț, SMA 20, SMA 50 și Benzi Bollinger
self.ax1.plot(df.index, df['Close'], label='Close', color='black', linewidth=1.2)
self.ax1.plot(df.index, df['SMA_20'], label='SMA 20', color='blue', linestyle='--', alpha=0.7)
self.ax1.plot(df.index, df['SMA_50'], label='SMA 50', color='orange', linestyle='--', alpha=0.7)
self.ax1.plot(df.index, df['BB_Upper'], label='BB Upper', color='gray', linestyle=':', alpha=0.5)
self.ax1.plot(df.index, df['BB_Lower'], label='BB Lower', color='gray', linestyle=':', alpha=0.5)
self.ax1.fill_between(df.index, df['BB_Lower'], df['BB_Upper'], color='gray', alpha=0.1)
self.ax1.set_title(f"{ticker_symbol} - Preț & Indicatori", fontsize=10)
self.ax1.legend(loc="upper left", fontsize=7)
self.ax1.grid(True, linestyle=':', alpha=0.6)
# Grafic 2: RSI cu nivelurile 30 și 70
self.ax2.plot(df.index, df['RSI'], label='RSI (14)', color='purple')
self.ax2.axhline(70, color='red', linestyle='--', alpha=0.6) # Overbought
self.ax2.axhline(30, color='green', linestyle='--', alpha=0.6) # Oversold
self.ax2.set_ylim(0, 100)
self.ax2.set_title("RSI (14)", fontsize=9)
self.ax2.grid(True, linestyle=':', alpha=0.6)
self.fig.autofmt_xdate(rotation=30)
self.fig.tight_layout()
self.canvas.draw()
if __name__ == "__main__":
root = tk.Tk()
app = FinancialDashboardApp(root)
root.mainloop()