Yahoo Finance Dashboard Using einteract

Abdul SaboorAbdul Saboor
2 min read
import datetime
import pandas as pd
import numpy as np
import yfinance as yf
import matplotlib.pyplot as plt

import ipywidgets as widgets
import einteract as ei
import traitlets

def rsi(series: pd.Series, window: int = 14) -> pd.Series:
    delta = series.diff()
    up = delta.clip(lower=0)
    down = -delta.clip(upper=0)
    ma_up = up.rolling(window).mean()
    ma_down = down.rolling(window).mean()
    rs = ma_up / (ma_down + 1e-12)
    return 100 - (100 / (1 + rs))

class FinanceDashboard(ei.InteractBase):
    def _interactive_params(self):
        today = datetime.date.today()
        default_start = today - datetime.timedelta(days=365)
        return dict(
            ticker = 'AAPL',
            start = widgets.DatePicker(value=default_start, description='Start:'),
            end = widgets.DatePicker(value=today, description='End:'),
            load = widgets.Button(description='Fetch Data', button_style='primary'),
            show_sma = True,
            sma_window = (1,50),
            show_ema = False,
            ema_span = (1,50),
            show_rsi = True,
            plot = widgets.Button(description='Plot Data', button_style='primary')
        )

    def __init__(self, *args,**kwargs):
        self.df = None # initial
        super().__init__(*args, **kwargs)
        self.relayout(
            left_sidebar=self.groups.controls, 
            center = self.groups.outputs, pane_widths=[1,2,0],height='400px'
        )
        self.set_css({
            "grid-gap": "8px",
            "button": {"margin-left": "90px"},
            "> *":{
                "border-radius": "8px",
                "background":"whitesmoke",
                "padding": "8px",
            }
        })

    @ei.callback
    def fetch_history(self, ticker, start, end, load):
        """
        Fetch historical daily OHLCV using yfinance.
        start and end are 'YYYY-MM-DD' strings.
        """
        if not ticker or ticker.strip() == "":
            self.df = pd.DataFrame()
        df = yf.download(ticker.strip(), start=start, end=end, progress=False, auto_adjust=True)
        if not df.empty:
            # ensure datetime index
            df.index = pd.to_datetime(df.index)
        self.df = df
        self.params.load.button_style = "primary"
        self.params.plot.button_style = "danger"

    @ei.callback
    def _wran_load(self, ticker, start, end):
        self.params.load.button_style = "danger"

    @ei.callback
    def _wran_plot(self, ticker, show_sma, sma_window, show_ema, ema_span, show_rsi):
        self.params.plot.button_style = "danger"

    @ei.callback
    def plot_stock(self, ticker,
        show_sma, sma_window,
        show_ema, ema_span, show_rsi, plot
        ):
        self.params.plot.button_style = "primary"
        df  = self.df
        if df is None or df.empty:
            print(f"No data for '{ticker}'. Try another ticker or wider date range.")
            return

        price = df['Close']
        fig, axes = plt.subplots(2 if show_rsi else 1, 1, sharex=True, figsize=(6.5,3.8),
                                 gridspec_kw={'height_ratios': [3, 1]} if show_rsi else None)
        if not isinstance(axes, (list, np.ndarray)):
            axes = [axes]

        ax_price = axes[0]
        ax_price.plot(df.index, price, label='Close', linewidth=1.25)
        ax_price.set_ylabel('Price (adjusted)')
        ax_price.set_title(f"{ticker.upper()}{df.index.min().date()} to {df.index.max().date()}")

        if show_sma:
            s = price.rolling(sma_window, min_periods=1).mean()
            ax_price.plot(df.index, s, label=f"SMA{(sma_window)}", linestyle='--', linewidth=1)

        if show_ema:
            e = price.ewm(span=ema_span, adjust=False).mean()
            ax_price.plot(df.index, e, label=f"EMA{ema_span}", linestyle=':', linewidth=1)

        ax_price.legend(loc='upper left')
        ax_price.grid(alpha=0.3)

        if show_rsi:
            ax_rsi = axes[1]
            r = rsi(price)
            ax_rsi.plot(df.index, r, label='RSI(14)')
            ax_rsi.axhline(70, linestyle='--', alpha=0.6)
            ax_rsi.axhline(30, linestyle='--', alpha=0.6)
            ax_rsi.set_ylabel('RSI')
            ax_rsi.set_ylim(0, 100)
            ax_rsi.grid(alpha=0.3)

        plt.tight_layout()


fd = FinanceDashboard()
fd

Above code generates the following dashboard. It logically shows red buttons to load data or update plot if inputs change.

10
Subscribe to my newsletter

Read articles from Abdul Saboor directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abdul Saboor
Abdul Saboor