import dash
from dash import dcc, html, dash_table
from dash.dependencies import Input, Output
import pandas as pd
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# --- 1. DATA LOADING & PREPARATION ---
def load_data():
df = pd.read_csv("merged_stock_data_iso_format.csv")
# Convert dates to datetime objects (handling the dd/mm/yyyy format found in your data)
df['date'] = pd.to_datetime(df['date'], format='%d/%m/%Y')
# Ensure data is sorted chronologically
df = df.sort_values('date')
return df
df = load_data()
tickers = df['dataset_name'].unique()
min_date = df['date'].min().date()
max_date = df['date'].max().date()
# --- 2. APP INITIALIZATION ---
app = dash.Dash(__name__)
# --- 3. APP LAYOUT ---
# In Dash, we use Python classes that map directly to HTML elements
app.layout = html.Div([
html.Div([
# SIDEBAR PANEL
html.Div([
html.H2("Dashboard Settings", style={'color': '#2c3e50', 'marginTop': '0'}),
html.Label("Select a Stock Symbol", style={'fontWeight': 'bold', 'marginTop': '20px', 'display': 'block'}),
dcc.Dropdown(
id='ticker-dropdown',
options=[{'label': t, 'value': t} for t in tickers],
value=tickers[0], # Set default value
clearable=False,
style={'marginBottom': '20px'}
),
html.Label("Select Date Range", style={'fontWeight': 'bold', 'display': 'block'}),
dcc.DatePickerRange(
id='date-picker',
min_date_allowed=min_date,
max_date_allowed=max_date,
start_date=min_date,
end_date=max_date,
display_format='YYYY-MM-DD',
style={'marginBottom': '20px', 'display': 'block', 'width': '100%'}
),
html.Label("Technical Indicators", style={'fontWeight': 'bold', 'display': 'block'}),
dcc.Checklist(
id='sma-checklist',
options=[
{'label': ' Show 20-Day SMA', 'value': 'SMA20'},
{'label': ' Show 50-Day SMA', 'value': 'SMA50'}
],
value=['SMA20'], # Default checked
style={'display': 'flex', 'flexDirection': 'column', 'gap': '10px'}
)
], style={'width': '25%', 'padding': '20px', 'backgroundColor': '#f8f9fa', 'borderRight': '1px solid #dee2e6'}),
# MAIN CONTENT PANEL
html.Div([
html.H1("📈 Interactive Stock Market Dashboard", style={'marginTop': '0', 'color': '#2c3e50'}),
# METRICS ROW (4 cards side-by-side)
html.Div([
html.Div(id='metric-price', style={'flex': '1', 'padding': '15px', 'backgroundColor': '#fff', 'border': '1px solid #dee2e6', 'marginRight': '10px', 'borderRadius': '5px'}),
html.Div(id='metric-high', style={'flex': '1', 'padding': '15px', 'backgroundColor': '#fff', 'border': '1px solid #dee2e6', 'marginRight': '10px', 'borderRadius': '5px'}),
html.Div(id='metric-low', style={'flex': '1', 'padding': '15px', 'backgroundColor': '#fff', 'border': '1px solid #dee2e6', 'marginRight': '10px', 'borderRadius': '5px'}),
html.Div(id='metric-volume', style={'flex': '1', 'padding': '15px', 'backgroundColor': '#fff', 'border': '1px solid #dee2e6', 'borderRadius': '5px'}),
], style={'display': 'flex', 'marginBottom': '20px'}),
# CHART
dcc.Graph(id='stock-chart', style={'height': '650px'}),
html.Hr(),
html.H3("Raw Data"),
# DATA TABLE
dash_table.DataTable(
id='raw-data-table',
page_size=10,
style_table={'overflowX': 'auto'},
style_cell={'textAlign': 'left', 'padding': '5px', 'fontFamily': 'Arial'},
style_header={'backgroundColor': '#f8f9fa', 'fontWeight': 'bold'}
)
], style={'width': '75%', 'padding': '20px', 'boxSizing': 'border-box'})
], style={'display': 'flex', 'fontFamily': 'Arial, sans-serif', 'minHeight': '100vh'})
])
# --- 4. CALLBACKS (INTERACTIVITY) ---
# This decorator listens to changes in the Inputs, and sends data to the Outputs
@app.callback(
[Output('metric-price', 'children'),
Output('metric-high', 'children'),
Output('metric-low', 'children'),
Output('metric-volume', 'children'),
Output('stock-chart', 'figure'),
Output('raw-data-table', 'data'),
Output('raw-data-table', 'columns')],
[Input('ticker-dropdown', 'value'),
Input('date-picker', 'start_date'),
Input('date-picker', 'end_date'),
Input('sma-checklist', 'value')]
)
def update_dashboard(selected_ticker, start_date, end_date, sma_options):
# 1. Filter by Ticker and Date
ticker_df = df[df['dataset_name'] == selected_ticker].copy()
ticker_df = ticker_df[(ticker_df['date'] >= start_date) & (ticker_df['date'] <= end_date)]
if ticker_df.empty:
# Return empty states if no data
return ["N/A"] * 4 + [go.Figure()] + [[], []]
# 2. Calculate Indicators
if 'SMA20' in sma_options:
ticker_df['SMA_20'] = ticker_df['close'].rolling(window=20).mean().round(2)
if 'SMA50' in sma_options:
ticker_df['SMA_50'] = ticker_df['close'].rolling(window=50).mean().round(2)
# 3. Calculate Metric Cards
current_price = ticker_df['close'].iloc[-1]
prev_price = ticker_df['close'].iloc[-2] if len(ticker_df) > 1 else current_price
price_change = current_price - prev_price
pct_change = (price_change / prev_price) * 100 if prev_price != 0 else 0
color = "#27ae60" if price_change >= 0 else "#c0392b"
sign = "+" if price_change >= 0 else ""
metric_price = html.Div([
html.H4("Current Price (Close)", style={'margin': '0 0 10px 0', 'color': '#7f8c8d', 'fontSize': '14px'}),
html.H2(f"{current_price:,.2f}", style={'margin': '0 0 5px 0'}),
html.Span(f"{sign}{price_change:,.2f} ({sign}{pct_change:.2f}%)", style={'color': color, 'fontWeight': 'bold'})
])
metric_high = html.Div([
html.H4("Period High", style={'margin': '0 0 10px 0', 'color': '#7f8c8d', 'fontSize': '14px'}),
html.H2(f"{ticker_df['high'].max():,.2f}", style={'margin': '0'})
])
metric_low = html.Div([
html.H4("Period Low", style={'margin': '0 0 10px 0', 'color': '#7f8c8d', 'fontSize': '14px'}),
html.H2(f"{ticker_df['low'].min():,.2f}", style={'margin': '0'})
])
metric_volume = html.Div([
html.H4("Total Volume", style={'margin': '0 0 10px 0', 'color': '#7f8c8d', 'fontSize': '14px'}),
html.H2(f"{ticker_df['volume'].sum():,}", style={'margin': '0'})
])
# 4. Generate Plotly Figure
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
vertical_spacing=0.03, row_heights=[0.7, 0.3])
fig.add_trace(go.Candlestick(x=ticker_df['date'], open=ticker_df['open'], high=ticker_df['high'],
low=ticker_df['low'], close=ticker_df['close'], name='Price'), row=1, col=1)
if 'SMA20' in sma_options:
fig.add_trace(go.Scatter(x=ticker_df['date'], y=ticker_df['SMA_20'], line=dict(color='#f39c12', width=1.5), name='SMA 20'), row=1, col=1)
if 'SMA50' in sma_options:
fig.add_trace(go.Scatter(x=ticker_df['date'], y=ticker_df['SMA_50'], line=dict(color='#2980b9', width=1.5), name='SMA 50'), row=1, col=1)
volume_colors = ['#26A69A' if close >= open else '#EF5350' for close, open in zip(ticker_df['close'], ticker_df['open'])]
fig.add_trace(go.Bar(x=ticker_df['date'], y=ticker_df['volume'], marker_color=volume_colors, name='Volume'), row=2, col=1)
fig.update_layout(
margin=dict(l=0, r=0, t=30, b=0),
xaxis_rangeslider_visible=False,
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
)
fig.update_yaxes(title_text="Price", row=1, col=1)
fig.update_yaxes(title_text="Volume", row=2, col=1)
# 5. Format Data for Table
table_df = ticker_df.sort_values('date', ascending=False).copy()
table_df['date'] = table_df['date'].dt.strftime('%Y-%m-%d')
columns = [{"name": i.title().replace("_", " "), "id": i} for i in table_df.columns]
data = table_df.to_dict('records')
return metric_price, metric_high, metric_low, metric_volume, fig, data, columns
# --- 5. RUN APP IN COLAB ---
if __name__ == '__main__':
# "inline" opens the app directly beneath the cell.
# Change to "external" if you want a clickable link to open it in a full new tab.
app.run(debug=True)