Spaces:
Running
Running
import streamlit as st | |
import yfinance as yf | |
import pandas as pd | |
import plotly.graph_objects as go | |
from plotly.subplots import make_subplots | |
import datetime as dt | |
# Page config | |
st.set_page_config( | |
page_title="Stock Analysis Dashboard", | |
page_icon="π", | |
layout="wide" | |
) | |
# Sidebar | |
st.sidebar.header("Settings") | |
ticker = st.sidebar.text_input("Ticker Symbol", "AAPL").upper() | |
start = st.sidebar.date_input("Start Date", dt.date.today() - dt.timedelta(days=365)) | |
end = st.sidebar.date_input("End Date", dt.date.today()) | |
# Cache data | |
def fetch_data(ticker, start, end): | |
return yf.download(ticker, start=start, end=end) | |
# Main | |
st.title("π Stock Analysis Dashboard") | |
st.subheader(f"{ticker} ({start} β {end})") | |
# Fetch | |
data = fetch_data(ticker, start, end) | |
if data.empty: | |
st.error("No data found. Check ticker or date range.") | |
st.stop() | |
# KPIs | |
latest_close = data["Close"].iloc[-1] | |
prev_close = data["Close"].iloc[-2] | |
change = latest_close - prev_close | |
pct_change = (change / prev_close) * 100 | |
col1, col2, col3, col4 = st.columns(4) | |
col1.metric("Close Price", f"${latest_close:,.2f}", f"{pct_change:+.2f}%") | |
col2.metric("Volume", f"{data['Volume'].iloc[-1]:,.0f}") | |
col3.metric("High", f"${data['High'].max():,.2f}") | |
col4.metric("Low", f"${data['Low'].min():,.2f}") | |
# Chart | |
fig = make_subplots( | |
rows=2, cols=1, | |
shared_xaxes=True, | |
vertical_spacing=0.03, | |
subplot_titles=("Price & Volume", "Volume"), | |
row_heights=[0.7, 0.3] | |
) | |
fig.add_trace( | |
go.Candlestick( | |
x=data.index, | |
open=data["Open"], | |
high=data["High"], | |
low=data["Low"], | |
close=data["Close"], | |
name="Candle" | |
), | |
row=1, col=1 | |
) | |
fig.add_trace( | |
go.Bar( | |
x=data.index, | |
y=data["Volume"], | |
name="Volume", | |
marker_color="#636EFA" | |
), | |
row=2, col=1 | |
) | |
fig.update_layout( | |
title=f"{ticker} Price & Volume", | |
xaxis_rangeslider_visible=False, | |
template="plotly_dark", | |
height=600, | |
showlegend=False | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
# Moving averages | |
ma_window = st.sidebar.slider("Moving Average Window", 5, 200, 20) | |
data[f"MA{ma_window}"] = data["Close"].rolling(ma_window).mean() | |
st.subheader("Moving Average") | |
ma_fig = go.Figure() | |
ma_fig.add_trace(go.Scatter(x=data.index, y=data["Close"], name="Close")) | |
ma_fig.add_trace(go.Scatter(x=data.index, y=data[f"MA{ma_window}"], name=f"MA{ma_window}")) | |
ma_fig.update_layout(title=f"{ticker} Close vs MA{ma_window}", height=400) | |
st.plotly_chart(ma_fig, use_container_width=True) | |
# Download | |
def convert_df(df): | |
return df.to_csv().encode("utf-8") | |
csv = convert_df(data) | |
st.sidebar.download_button( | |
label="Download CSV", | |
data=csv, | |
file_name=f"{ticker}_{start}_{end}.csv", | |
mime="text/csv" | |
) |