Created
August 15, 2025 23:01
-
-
Save NonLogicalDev/b555a285b9482b4a287c2ce6fe301dea to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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