Build an *Advanced* AI-Powered Technical Analysis Stock Dashboard with Google Gemini 2.0 Flash
Gemini 2.0 Flash + Streamlit = Technical Analysis Intelligence
A few months ago, I shared a video exploring the use of LLM vision models to interpret stock market technical indicator charts. The case study featured Meta’s Llama 3.2 Vision model running locally via an Ollama server. I aimed to make this accessible to those with older hardware—like my five-year-old MacBook—without relying on paid services from OpenAI or Google.
But it turns out the Llama 3.2 Vision model kinda sucks for this task.
So, I decided to revisit the idea with the latest multimodal Google Gemini 2.0 models, with a focus on automation and scale. In this tutorial, we’ll prioritize performance over cost and build an updated AI-powered technical analysis dashboard that delivers buy/hold/sell decisions for your entire portfolio within seconds.
Full Code Below the Video
Important Note: This video is not financial or investing advice. It is an educational tutorial on how to use AI/LLM vision models in Python. Also, don't blindly trust the results of LLM model results without critical thinking or subject matter expertise 🧠. LLM's are still experimental technology that have high error rates.
Premium Code (NEW)
Includes additional technical indicators, ability to quickly customize parameters of technical indicators, and advanced charting
Purchase Here: https://deep-charts-shop.fourthwall.com/products/premium-ai-powered-technical-analysis-dashboard-python-starter-template
Full Code (As Seen In Tutorial)
## AI-Powered Technical Analysis Dashboard (Gemini 2.0)
## Source: https://www.youtube.com/@DeepCharts
# Libraries
import streamlit as st
import yfinance as yf
import pandas as pd
import plotly.graph_objects as go
import google.generativeai as genai
import tempfile
import os
import json
from datetime import datetime, timedelta
# Configure the API key - IMPORTANT: Use Streamlit secrets or environment variables for security
# For now, using hardcoded API key - REPLACE WITH YOUR ACTUAL API KEY SECURELY
GOOGLE_API_KEY = "your api key goes here"
genai.configure(api_key=GOOGLE_API_KEY)
# Select the Gemini model - using 'gemini-2.0-flash' as a general-purpose model
MODEL_NAME = 'gemini-2.0-flash' # or other model
gen_model = genai.GenerativeModel(MODEL_NAME)
# Set up Streamlit app
st.set_page_config(layout="wide")
st.title("AI-Powered Technical Stock Analysis Dashboard")
st.sidebar.header("Configuration")
# Input for multiple stock tickers (comma-separated)
tickers_input = st.sidebar.text_input("Enter Stock Tickers (comma-separated):", "AAPL,MSFT,GOOG")
# Parse tickers by stripping extra whitespace and splitting on commas
tickers = [ticker.strip().upper() for ticker in tickers_input.split(",") if ticker.strip()]
# Set the date range: start date = one year before today, end date = today
end_date_default = datetime.today()
start_date_default = end_date_default - timedelta(days=365)
start_date = st.sidebar.date_input("Start Date", value=start_date_default)
end_date = st.sidebar.date_input("End Date", value=end_date_default)
# Technical indicators selection (applied to every ticker)
st.sidebar.subheader("Technical Indicators")
indicators = st.sidebar.multiselect(
"Select Indicators:",
["20-Day SMA", "20-Day EMA", "20-Day Bollinger Bands", "VWAP"],
default=["20-Day SMA"]
)
# Button to fetch data for all tickers
if st.sidebar.button("Fetch Data"):
stock_data = {}
for ticker in tickers:
# Download data for each ticker using yfinance
data = yf.download(ticker, start=start_date, end=end_date)
if not data.empty:
stock_data[ticker] = data
else:
st.warning(f"No data found for {ticker}.")
st.session_state["stock_data"] = stock_data
st.success("Stock data loaded successfully for: " + ", ".join(stock_data.keys()))
# Ensure we have data to analyze
if "stock_data" in st.session_state and st.session_state["stock_data"]:
# Define a function to build chart, call the Gemini API and return structured result
def analyze_ticker(ticker, data):
# Build candlestick chart for the given ticker's data
fig = go.Figure(data=[
go.Candlestick(
x=data.index,
open=data['Open'],
high=data['High'],
low=data['Low'],
close=data['Close'],
name="Candlestick"
)
])
# Add selected technical indicators
def add_indicator(indicator):
if indicator == "20-Day SMA":
sma = data['Close'].rolling(window=20).mean()
fig.add_trace(go.Scatter(x=data.index, y=sma, mode='lines', name='SMA (20)'))
elif indicator == "20-Day EMA":
ema = data['Close'].ewm(span=20).mean()
fig.add_trace(go.Scatter(x=data.index, y=ema, mode='lines', name='EMA (20)'))
elif indicator == "20-Day Bollinger Bands":
sma = data['Close'].rolling(window=20).mean()
std = data['Close'].rolling(window=20).std()
bb_upper = sma + 2 * std
bb_lower = sma - 2 * std
fig.add_trace(go.Scatter(x=data.index, y=bb_upper, mode='lines', name='BB Upper'))
fig.add_trace(go.Scatter(x=data.index, y=bb_lower, mode='lines', name='BB Lower'))
elif indicator == "VWAP":
data['VWAP'] = (data['Close'] * data['Volume']).cumsum() / data['Volume'].cumsum()
fig.add_trace(go.Scatter(x=data.index, y=data['VWAP'], mode='lines', name='VWAP'))
for ind in indicators:
add_indicator(ind)
fig.update_layout(xaxis_rangeslider_visible=False)
# Save chart as temporary PNG file and read image bytes
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmpfile:
fig.write_image(tmpfile.name)
tmpfile_path = tmpfile.name
with open(tmpfile_path, "rb") as f:
image_bytes = f.read()
os.remove(tmpfile_path)
# Create an image Part
image_part = {
"data": image_bytes,
"mime_type": "image/png"
}
# Updated prompt asking for a detailed justification of technical analysis and a recommendation.
analysis_prompt = (
f"You are a Stock Trader specializing in Technical Analysis at a top financial institution. "
f"Analyze the stock chart for {ticker} based on its candlestick chart and the displayed technical indicators. "
f"Provide a detailed justification of your analysis, explaining what patterns, signals, and trends you observe. "
f"Then, based solely on the chart, provide a recommendation from the following options: "
f"'Strong Buy', 'Buy', 'Weak Buy', 'Hold', 'Weak Sell', 'Sell', or 'Strong Sell'. "
f"Return your output as a JSON object with two keys: 'action' and 'justification'."
)
# Call the Gemini API with text and image input - Roles added: "user" for both text and image
contents = [
{"role": "user", "parts": [analysis_prompt]}, # Text prompt with role "user"
{"role": "user", "parts": [image_part]} # Image part with role "user"
]
response = gen_model.generate_content(
contents=contents # Pass the restructured 'contents' with roles
)
try:
# Attempt to parse JSON from the response text
result_text = response.text
# Find the start and end of the JSON object within the text (if Gemini includes extra text)
json_start_index = result_text.find('{')
json_end_index = result_text.rfind('}') + 1 # +1 to include the closing brace
if json_start_index != -1 and json_end_index > json_start_index:
json_string = result_text[json_start_index:json_end_index]
result = json.loads(json_string)
else:
raise ValueError("No valid JSON object found in the response")
except json.JSONDecodeError as e:
result = {"action": "Error", "justification": f"JSON Parsing error: {e}. Raw response text: {response.text}"}
except ValueError as ve:
result = {"action": "Error", "justification": f"Value Error: {ve}. Raw response text: {response.text}"}
except Exception as e:
result = {"action": "Error", "justification": f"General Error: {e}. Raw response text: {response.text}"}
return fig, result
# Create tabs: first tab for overall summary, subsequent tabs per ticker
tab_names = ["Overall Summary"] + list(st.session_state["stock_data"].keys())
tabs = st.tabs(tab_names)
# List to store overall results
overall_results = []
# Process each ticker and populate results
for i, ticker in enumerate(st.session_state["stock_data"]):
data = st.session_state["stock_data"][ticker]
# Analyze ticker: get chart figure and structured output result
fig, result = analyze_ticker(ticker, data)
overall_results.append({"Stock": ticker, "Recommendation": result.get("action", "N/A")})
# In each ticker-specific tab, display the chart and detailed justification
with tabs[i + 1]:
st.subheader(f"Analysis for {ticker}")
st.plotly_chart(fig)
st.write("**Detailed Justification:**")
st.write(result.get("justification", "No justification provided."))
# In the Overall Summary tab, display a table of all results
with tabs[0]:
st.subheader("Overall Structured Recommendations")
df_summary = pd.DataFrame(overall_results)
st.table(df_summary)
else:
st.info("Please fetch stock data using the sidebar.")
Subscribe to the Deep Charts YouTube Channel for more informative AI and Machine Learning Tutorials.
FYI, yfinance has made some updates over the past few months. Change the following in your code:
set yfinance to yfinance==0.2.54 when installing.
add multi_level_index=False: "data = yf.download(ticker, start=start_date, end=end_date,multi_level_index=False)"
I'm interested to try, and will consider buying the premium version also. Will it be hard for me to use another Ai model, such as Deepseek? Because I will need vpn and face region blocks for Google Ai. Thank you !