Python Crypto Market Round-Up App | CTkinter GUI + Live CoinGecko API | FuzzuTech
Demo :
Click Video πππ
Features
✔ Weekly Crypto Market Round-Up App
✔ Python + CTkinter GUI
✔ Live Market Data (Top 10 Coins)
✔ Alerts for Top Movers
✔ Export Summary to Clipboard
✔ Embedded 7-Day Price Chart
Code :
"""
FuzzuTech - Python Crypto Market Round-Up (CTkinter GUI)
File: fuzzu_crypto_roundup.py
Author: FuzzuTech (Generated for user)
Description:
A modern, stylish, single-file CTkinter GUI application that fetches
crypto market data from the CoinGecko API and shows a weekly market
round-up: top movers, small summary, sparkline-like chart for selected
coins, and quick alerts.
Features:
- Fetch top N coins by market cap (default 10)
- Show price, 24h change, market cap, and sparkline (mini preview)
- Select a coin to view a 7-day chart (matplotlib embedded)
- Export quick summary to clipboard
- Modern styling using customtkinter
Requirements:
- Python 3.8+
- pip install customtkinter requests matplotlib pillow
Run:
python fuzzu_crypto_roundup.py
Note: This app uses CoinGecko public API (no API key). Internet required to fetch live data.
If you plan to deploy, respect CoinGecko rate limits.
"""
import threading
import requests
import time
import io
from datetime import datetime
from PIL import Image, ImageTk
import customtkinter as ctk
from tkinter import ttk, messagebox
# Matplotlib embedding
import matplotlib
matplotlib.use('Agg') # safe backend for image generation
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# ----------------------------- Configuration -----------------------------
API_BASE = 'https://api.coingecko.com/api/v3'
TOP_N = 10
CURRENCY = 'usd'
REFRESH_INTERVAL = 180 # seconds
# ----------------------------- Utility Functions --------------------------
def format_usd(x):
try:
if x is None:
return "-"
if x >= 1:
return f"${x:,.2f}"
else:
return f"${x:.8f}"
except Exception:
return str(x)
def fetch_top_coins(vs_currency='usd', per_page=TOP_N):
"""Fetch top coins by market cap with sparkline data."""
url = f"{API_BASE}/coins/markets"
params = {
'vs_currency': vs_currency,
'order': 'market_cap_desc',
'per_page': per_page,
'page': 1,
'sparkline': 'true',
'price_change_percentage': '24h,7d'
}
r = requests.get(url, params=params, timeout=15)
r.raise_for_status()
return r.json()
def fetch_market_chart(coin_id, vs_currency='usd', days=7):
"""Fetch market chart (price history) for coin_id for given days."""
url = f"{API_BASE}/coins/{coin_id}/market_chart"
params = {'vs_currency': vs_currency, 'days': days}
r = requests.get(url, params=params, timeout=15)
r.raise_for_status()
return r.json()
# ----------------------------- GUI App -----------------------------------
class CryptoRoundupApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("FuzzuTech — Crypto Market Round-Up")
self.geometry("1020x680")
self.minsize(920, 600)
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
# Data state
self.coins = []
self.selected_coin = None
self.last_updated = None
# Layout
self._create_header()
self._create_body()
self._start_background_refresh()
def _create_header(self):
header = ctk.CTkFrame(self, height=72)
header.pack(fill='x', padx=12, pady=(12, 6))
title = ctk.CTkLabel(header, text="Crypto Market Round-Up", font=ctk.CTkFont(size=20, weight="bold"))
title.place(x=14, y=12)
subtitle = ctk.CTkLabel(header, text="Trends, Tokens & What to Watch — Weekly Snapshot", font=ctk.CTkFont(size=12))
subtitle.place(x=16, y=38)
self.update_label = ctk.CTkLabel(header, text="Last update: --", anchor='e')
self.update_label.place(relx=0.98, rely=0.5, anchor='e')
refresh_btn = ctk.CTkButton(header, text='Refresh Now', corner_radius=8, command=self.manual_refresh)
refresh_btn.place(relx=0.85, rely=0.5, anchor='center')
copy_btn = ctk.CTkButton(header, text='Copy Summary', corner_radius=8, command=self.copy_summary)
copy_btn.place(relx=0.94, rely=0.5, anchor='center')
def _create_body(self):
body = ctk.CTkFrame(self)
body.pack(fill='both', expand=True, padx=12, pady=(6,12))
left = ctk.CTkFrame(body, width=340)
left.pack(side='left', fill='y', padx=(0,12), pady=6)
right = ctk.CTkFrame(body)
right.pack(side='right', fill='both', expand=True, pady=6)
# Left: list of top coins
search_frame = ctk.CTkFrame(left, height=48)
search_frame.pack(fill='x', padx=6, pady=(6,4))
self.search_var = ctk.StringVar()
search = ctk.CTkEntry(search_frame, placeholder_text='Search coin (name or symbol)...', textvariable=self.search_var)
search.pack(fill='x', padx=6, pady=6)
search.bind('<KeyRelease>', lambda e: self._render_coin_list())
self.coin_listbox = ctk.CTkScrollableFrame(left)
self.coin_listbox.pack(fill='both', expand=True, padx=6, pady=6)
# Right: details and chart
detail_top = ctk.CTkFrame(right, height=240)
detail_top.pack(fill='x', padx=6, pady=(6,4))
self.detail_title = ctk.CTkLabel(detail_top, text='Select a coin to view details', font=ctk.CTkFont(size=16, weight='bold'))
self.detail_title.pack(anchor='w', padx=8, pady=(8,2))
self.detail_info = ctk.CTkLabel(detail_top, text='', justify='left')
self.detail_info.pack(anchor='w', padx=8)
chart_frame = ctk.CTkFrame(right)
chart_frame.pack(fill='both', expand=True, padx=6, pady=6)
# Matplotlib figure placeholder
self.fig = Figure(figsize=(6,3), dpi=100)
self.ax = self.fig.add_subplot(111)
self.ax.set_facecolor('#0f1720')
self.ax.grid(alpha=0.2)
self.canvas = FigureCanvasTkAgg(self.fig, master=chart_frame)
self.canvas.get_tk_widget().pack(fill='both', expand=True)
# Footer quick notes
footer = ctk.CTkFrame(right, height=40)
footer.pack(fill='x', padx=6, pady=(4,6))
self.alert_label = ctk.CTkLabel(footer, text='Alerts: No critical movers detected')
self.alert_label.pack(anchor='w', padx=8)
def manual_refresh(self):
threading.Thread(target=self._refresh_data, daemon=True).start()
def _start_background_refresh(self):
# initial fetch
threading.Thread(target=self._refresh_data, daemon=True).start()
# schedule next
self.after(REFRESH_INTERVAL * 1000, self._start_background_refresh)
def _refresh_data(self):
try:
coins = fetch_top_coins(vs_currency=CURRENCY, per_page=TOP_N)
self.coins = coins
self.last_updated = datetime.utcnow()
self._render_coin_list()
self.update_label.configure(text=f"Last update: {self.last_updated.strftime('%Y-%m-%d %H:%M:%S')} UTC")
# Show quick alerts if big movers (>10% 24h)
movers = [c for c in coins if abs(c.get('price_change_percentage_24h') or 0) > 10]
if movers:
names = ', '.join([m['symbol'].upper() for m in movers[:4]])
self.alert_label.configure(text=f"Alerts: High 24h moves — {names} ...")
else:
self.alert_label.configure(text='Alerts: No critical movers detected')
except Exception as e:
print('Error fetching data:', e)
messagebox.showerror('Network / API Error', f'Could not fetch market data.\n{e}')
def _render_coin_list(self):
# Clear list
for w in self.coin_listbox.winfo_children():
w.destroy()
q = self.search_var.get().strip().lower()
coins = self.coins
if q:
coins = [c for c in coins if q in (c.get('id','') + c.get('symbol','') + c.get('name','')).lower()]
for coin in coins:
frame = ctk.CTkFrame(self.coin_listbox, height=64)
frame.pack(fill='x', padx=6, pady=6)
# left: image
try:
img_data = requests.get(coin['image'], timeout=10).content
pil = Image.open(io.BytesIO(img_data)).resize((42,42))
tkimg = ImageTk.PhotoImage(pil)
except Exception:
tkimg = None
if tkimg:
img_label = ctk.CTkLabel(frame, image=tkimg, text='')
img_label.image = tkimg
img_label.place(x=8, y=10)
name = ctk.CTkLabel(frame, text=f"{coin['name']} ({coin['symbol'].upper()})", anchor='w', font=ctk.CTkFont(size=12, weight='bold'))
name.place(x=64, y=6)
price = format_usd(coin.get('current_price'))
change24 = coin.get('price_change_percentage_24h')
change_text = f"{change24:+.2f}%" if change24 is not None else '-'
change_lbl = ctk.CTkLabel(frame, text=change_text)
change_lbl.place(x=64, y=28)
market_lbl = ctk.CTkLabel(frame, text=f"MCap: {format_usd(coin.get('market_cap'))}")
market_lbl.place(relx=0.98, x=-8, y=8, anchor='ne')
view_btn = ctk.CTkButton(frame, text='View', width=70, height=30, corner_radius=8, command=lambda c=coin: self._on_select_coin(c))
view_btn.place(relx=0.98, x=-8, y=34, anchor='ne')
if not coins:
empty = ctk.CTkLabel(self.coin_listbox, text='No coins match your search.')
empty.pack(padx=12, pady=12)
def _on_select_coin(self, coin):
self.selected_coin = coin
self.detail_title.configure(text=f"{coin['name']} ({coin['symbol'].upper()}) — {format_usd(coin.get('current_price'))}")
info = (
f"Market Cap: {format_usd(coin.get('market_cap'))}\n"
f"24h Change: {coin.get('price_change_percentage_24h'):+.2f}%\n"
f"7d Change: {coin.get('price_change_percentage_7d_in_currency') or '-'}%\n"
f"Total Volume: {format_usd(coin.get('total_volume'))}\n"
)
self.detail_info.configure(text=info)
threading.Thread(target=self._load_and_plot_coin, args=(coin,), daemon=True).start()
def _load_and_plot_coin(self, coin):
try:
data = fetch_market_chart(coin['id'], vs_currency=CURRENCY, days=7)
prices = data.get('prices', [])
if not prices:
raise ValueError('No price data returned')
times = [datetime.fromtimestamp(int(p[0]/1000)) for p in prices]
vals = [p[1] for p in prices]
# clear and plot
self.ax.cla()
self.ax.plot(times, vals, linewidth=2)
self.ax.set_title(f"7-Day Price — {coin['symbol'].upper()}")
self.ax.tick_params(axis='x', rotation=30)
self.fig.tight_layout()
# refresh canvas on main thread
self.canvas.draw()
except Exception as e:
print('Error loading chart:', e)
messagebox.showwarning('Chart Error', f'Could not load 7-day chart for {coin["name"]}.\n{e}')
def copy_summary(self):
if not self.coins:
messagebox.showinfo('No Data', 'No market data to summarise yet.')
return
lines = [f"Crypto Market Round-Up — {datetime.utcnow().strftime('%Y-%m-%d')}\n"]
for c in self.coins:
lines.append(f"{c['symbol'].upper()}: {format_usd(c.get('current_price'))} ({c.get('price_change_percentage_24h'):+.2f}%) — MCap {format_usd(c.get('market_cap'))}")
text = '\n'.join(lines)
try:
# copy to clipboard
self.clipboard_clear()
self.clipboard_append(text)
messagebox.showinfo('Copied', 'Summary copied to clipboard — paste anywhere!')
except Exception as e:
messagebox.showerror('Clipboard Error', f'Could not copy to clipboard.\n{e}')
# ----------------------------- Run App ----------------------------------
if __name__ == '__main__':
app = CryptoRoundupApp()
app.mainloop()
Comments
Post a Comment