Skip to content

Instantly share code, notes, and snippets.

@NonLogicalDev
Created August 15, 2025 23:01
Show Gist options
  • Select an option

  • Save NonLogicalDev/b555a285b9482b4a287c2ce6fe301dea to your computer and use it in GitHub Desktop.

Select an option

Save NonLogicalDev/b555a285b9482b4a287c2ce6fe301dea to your computer and use it in GitHub Desktop.
import math
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
import numpy as np
# import yfinance as yf
#
# # Define the stock tickers
# tickers = ['GOOGL', 'AMZN', 'MSFT', 'TSLA', 'UBER', 'NVDA', 'SPY', 'AAPL']
#
# # Fetch the latest stock data from Yahoo Finance
# stock_data = {ticker: yf.download(ticker, period='30d', interval='1d') for ticker in tickers}
#
# # conver to dict of lists of close prices
# stock_data_dict = {ticker: data['Close'] for ticker, data in stock_data.items()}
# stock_data_ref = {ticker: data['Close'].iloc[0].values[0] for ticker, data in stock_data.items()}
#
# from draw_stock_tickers import draw_ticker_grid
# fig = draw_ticker_grid(stock_data_dict, columns=2, width=600, height=400, subtitle_font_size=20)
#
# # Show the figure
# fig.show()
def draw_ticker_grid(stock_data_df, ref_data=None, columns=2, width=None, height=None, row_gap=0.05, col_gap=0.11, subtitle_font_size=14):
"""
Create a grid of stock price charts with reference lines.
Args:
stock_data_df (dict): Dictionary with ticker symbols as keys and pandas DataFrames as values
ref_data (dict, optional): Dictionary with ticker symbols as keys and reference values.
If None, uses mean of each stock's data
columns (int): Number of columns in the grid layout
Returns:
plotly.graph_objects.Figure: Configured subplot figure
"""
stock_data = stock_data_df
# Calculate reference data (first value)
if ref_data is None:
ref_data = {ticker: data.iloc[0].values[0] for ticker, data in stock_data.items()}
# Calculate grid dimensions
num_rows = math.ceil(len(stock_data) / columns)
num_cols = columns
# Create position mapping for each ticker
positions = [(i, j) for i in range(1, num_rows + 1) for j in range(1, num_cols + 1)]
tickers = list(stock_data.keys())
ticker_positions = dict(zip(tickers, positions))
# Initialize subplot figure
fig = make_subplots(rows=num_rows, cols=num_cols, vertical_spacing=row_gap, horizontal_spacing=col_gap)
fig.update_layout(
template='plotly_dark',
margin=dict(l=20, r=50, t=20, b=20) # Reduced padding on left, top, bottom
)
if width is not None:
fig.update_layout(width=width)
if height is not None:
fig.update_layout(height=height)
# Configure axes and annotations for each subplot
_configure_subplots(fig, ticker_positions, num_rows, num_cols, subtitle_font_size=subtitle_font_size)
# Define color schemes
colors = _get_color_schemes()
# Add data traces for each stock
for ticker, (row, col) in ticker_positions.items():
_add_stock_traces(fig, ticker, stock_data[ticker], ref_data[ticker],
row, col, colors)
return fig
def _configure_subplots(fig, ticker_positions, num_rows, num_cols, subtitle_font_size=14):
"""Configure axes and annotations for all subplots."""
for ticker, (row, col) in ticker_positions.items():
# Configure x-axis
fig.update_xaxes(
row=row, col=col,
visible=False,
showgrid=False,
showline=False,
showticklabels=False,
showspikes=True,
spikemode='across',
spikedash='solid',
spikesnap='cursor',
spikethickness=1
)
# Configure y-axis
fig.update_yaxes(visible=False, row=row, col=col)
# Add ticker label annotation
fig.add_annotation(
text=ticker,
x=0.5, y=0.5,
xref='x domain', yref='y domain',
showarrow=False,
font=dict(family='Arial Black', size=subtitle_font_size),
opacity=0.4,
row=row, col=col,
valign='middle',
xanchor='center',
yanchor='middle',
)
def _get_color_schemes():
"""Define color schemes for positive and negative performance."""
return {
'green': 'rgba(0,255,0,0.5)',
'red': 'rgba(255,0,0,0.5)',
'green_gradient': [[0, 'rgba(0,255,0,0.5)'], [1, 'rgba(0,255,0,0)']],
'red_gradient': [[0, 'rgba(255,0,0,0.5)'], [1, 'rgba(255,0,0,0)']]
}
def _add_stock_traces(fig, ticker, data, reference_value, row, col, colors):
"""Add price line and reference line traces for a single stock."""
# Clean and prepare data
clean_data = data.dropna()
if len(clean_data) == 0:
return
# Extract data components
dates = list(clean_data.index)
values = list(clean_data.iloc[:, 0].values)
data_min = clean_data.min().values[0]
data_max = clean_data.max().values[0]
current_value = clean_data.iloc[-1].values[0]
first_value = clean_data.iloc[0].values[0]
# Calculate price changes
price_change = current_value - first_value
percentage_change = (price_change / first_value) * 100 if first_value != 0 else 0
# Determine color scheme based on performance vs reference
is_positive = current_value > reference_value
line_color = 'green' if is_positive else 'red'
fill_gradient = colors['green_gradient'] if is_positive else colors['red_gradient']
reference_color = colors['green'] if is_positive else colors['red']
# Add main price trace with fill
fig.add_trace(
go.Scatter(
x=dates,
y=values,
name=ticker,
line=dict(color=line_color),
mode='lines',
fill='tonexty',
fillgradient=dict(
type='vertical',
colorscale=fill_gradient,
start=data_max,
stop=data_min,
),
showlegend=False,
connectgaps=False
),
row=row, col=col
)
# Add reference line
fig.add_trace(
go.Scatter(
x=dates,
y=[reference_value] * len(dates),
mode='lines',
name=f'{ticker} Average',
line=dict(color=reference_color, dash='dot'),
showlegend=False,
connectgaps=False
),
row=row, col=col
)
# Set y-axis range to data bounds
fig.update_yaxes(
row=row, col=col,
range=[data_min, data_max]
)
# Add price change annotation on the right side
change_color = 'green' if price_change >= 0 else 'red'
change_symbol = '+' if price_change >= 0 else ''
fig.add_annotation(
text=f"{change_symbol}{price_change:.2f}<br>{percentage_change:.2f}%",
x=1.0, # Moved further right to avoid collision
y=0.5, # Center vertically
xref='x domain',
yref='y domain',
showarrow=False,
font=dict(family='Arial', color=change_color),
opacity=0.8,
row=row, col=col,
valign='middle',
xanchor='left',
yanchor='middle',
bgcolor='rgba(0,0,0,0.3)', # Semi-transparent background
bordercolor=change_color,
borderwidth=1
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment