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

Popular posts from this blog

πŸš€ Simple Login & Registration System in Python Tkinter πŸ“±

πŸ“‘ Fuzzu Packet Sniffer – Python GUI for Real-Time IP Monitoring | Tkinter + Scapy

πŸ”₯ Advanced MP3 Music Player in Python | CustomTkinter + Pygame | Free Source Code