Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stock chatbot #124

Merged
merged 30 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2c3fb29
draft 1
lfunderburk Feb 25, 2024
e06a59d
incorporate duckdb database instance to speed up queries
lfunderburk Feb 25, 2024
1d1aead
incorporate nl to sql logic
lfunderburk Feb 26, 2024
b78ea14
add plotting functionality
lfunderburk Feb 26, 2024
a5a6503
add plotting functionality
lfunderburk Feb 26, 2024
8168133
add plot interpretation
lfunderburk Feb 26, 2024
2c3743f
add image interpreter
lfunderburk Feb 26, 2024
ae46f29
Update .gitignore
lfunderburk Feb 26, 2024
918e4f3
fix data structure for llminterpretation
lfunderburk Feb 26, 2024
1a57fe1
add interpretation area
lfunderburk Feb 26, 2024
86b07e4
finalize application
lfunderburk Feb 29, 2024
1de2d93
add missing requirements
lfunderburk Feb 29, 2024
0d3ba78
remove unused plot file
lfunderburk Feb 29, 2024
89d6a9b
incorporate readme
lfunderburk Feb 29, 2024
042c7c6
redeploy using plotly instead
lfunderburk Feb 29, 2024
3ee2a27
incorporate colour into plot
lfunderburk Feb 29, 2024
52d9273
test app functionality and finalize
lfunderburk Feb 29, 2024
6d1703b
fix plot source
lfunderburk Feb 29, 2024
fdda92e
delete Dockerfile
lfunderburk Feb 29, 2024
2f2257b
remove conda commands
lfunderburk Feb 29, 2024
37a0f20
add dependency versions
lfunderburk Feb 29, 2024
26ef1bd
remove hvplot reference
lfunderburk Feb 29, 2024
5e267eb
restore previous example dependencies
lfunderburk Feb 29, 2024
4e20c5c
ensure answer is cleared
lfunderburk Feb 29, 2024
5e842bf
remove duckdb file
lfunderburk Feb 29, 2024
5134a06
remove dotenv references
lfunderburk Feb 29, 2024
ed43700
add instructions to obtain tickers
lfunderburk Feb 29, 2024
5deed44
Delete examples/panel/stock-market-chatbot/nasdaq_symbols.csv
lfunderburk Feb 29, 2024
aeaf233
remove global gitignore changes
lfunderburk Feb 29, 2024
fd5c343
fix readme closing brackets
lfunderburk Feb 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,4 @@ cython_debug/
.DS_Store
*.bkp
*.dtmp
wandb/

4 changes: 4 additions & 0 deletions examples/panel/stock-market-chatbot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

!examples/panel/stock-market-chatbot/nasdaq_symbols.csv

*.duckdb
57 changes: 57 additions & 0 deletions examples/panel/stock-market-chatbot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## LLM-powered Stock Market app with Panel

This application downloads stock market data from Yahoo Finance, populates a duckdb instance, generates a time series plot of the selected stocks. It also allows the user to ask a natural language question about the plot and get a response using the LLM model.

The app is built using Python Yahoo Finance [yfinance](https://pypi.org/project/yfinance/), [Panel](https://panel.holoviz.org/),[DuckDB](https://duckdb.org/), OpenAI's [Vision Model preview API](https://platform.openai.com/docs/guides/vision) and [ImageKit](https://docs.imagekit.io/getting-started/quickstart-guides/python/python_app). The app can be hosted on [Ploomber Cloud](https://www.platform.ploomber.io/).

The app will store the plot generated and save it to ImageKit.io, and then use the OpenAI API to generate a response to the user's question about the plot.

### Pre-requisites

1. OpenAI API key. Visit their [Documentation](https://platform.openai.com/docs/api-reference/introduction)
2. ImageKit.io url endpoint, public key, and private key. Visit their [Dashboard](https://imagekit.io/dashboard)
edublancas marked this conversation as resolved.
Show resolved Hide resolved

Save your OpenAI API key in an environment variable. You can set it as an environment variable from the terminal as follows:

```bash
export OPENAI_API_KEY=your_api_key
export image_private_key=your-imagekit-private-key
export image_public_key=your-imagekit-public-key
export image_url_endpoint=your-imagekit-endpoint
```

To run the app, you need to install the following packages:

```bash
pip install -r requirements.txt
```

Download the tickers. You can use the `nasdaq_symbols.csv` in this repository. This file was obtained from [the NASDAQ site](https://www.nasdaq.com/market-activity/stocks/screener) - press download.

If you want to use different tickers, ensure to replace the `nasdaq_symbols.csv` file with your file and update the `get_stock_symbols` function in the `stock.py` file.

```python
def get_stock_symbols():
# Symbols obtained from
# https://www.nasdaq.com/market-activity/stocks/screener
data = pd.read_csv("nasdaq_symbols.csv")
symbols = data["Symbol"].to_list()
names = data['Name'].to_list()

symbol_name = {symbol: name for symbol, name in zip(symbols, names)}

return symbols, symbol_name
```

### Running the app

```bash
panel serve app.py --autoreload --show
```

### Deploying the app to Ploomber Cloud

To deploy the app to Ploomber Cloud, you need to have a Ploomber Cloud account. Visit their [website](https://www.platform.ploomber.io/) to create an account. Once you have an account, you can deploy the Panel app to Ploomber Cloud using the following guides:

[Deploy Panel apps through the UI](https://docs.cloud.ploomber.io/en/latest/apps/panel.html).
[Add your secret variables](https://docs.cloud.ploomber.io/en/latest/user-guide/env-vars.html)
230 changes: 230 additions & 0 deletions examples/panel/stock-market-chatbot/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import panel as pn
import pandas as pd
from chat import analyze_image_with_text

import os
from imagekitio import ImageKit
import nest_asyncio

from stock import store_data_in_duckdb, get_data_from_duckdb, get_stock_symbols
from bokeh.themes import Theme
from bokeh.io import curdoc

import plotly.express as px
pd.options.plotting.backend = "plotly"

img_private = os.getenv("image_private_key")
img_public = os.getenv("image_public_key")
img_endpoint = os.getenv("image_url_endpoint")

curdoc().theme = Theme(json={})


# needed because Panel starts up the ioloop
nest_asyncio.apply()

# Initialize Panel with extensions for plotting
pn.extension('plotly')


def save_image():
"""

Content of upload object

{
'fileId': '6311960051c0c0bdd51cff53',
'name': 'test-url_9lQZRkh8J.jpg',
'size': 1222,
'versionInfo': {
'id': '6311960051c0c0bdd51cff53',
'name': 'Version 1'
},
'filePath': '/test-url_9lQZRkh8J.jpg',
'url': 'https://ik.imagekit.io/your_imagekit_id/test-url_9lQZRkh8J.jpg',
'fileType': 'non-image',
'tags': ['tag1', 'tag2'],
'AITags': None,
'isPrivateFile': False
}

"""
imagekit = ImageKit(
private_key=img_private,
public_key=img_public,
url_endpoint = img_endpoint
)

upload = imagekit.upload_file(
file=open("plot_image.png", "rb"),
file_name="test-file.jpg",
)

result = upload.response_metadata.raw

return result['url']

def save_plot(plot, filename="plot.png"):
plot.write_image(filename)

def update_visualization(ticker, start, end, data_instruction, question):
# Fetch the stock data from DuckDB
print(type(start))
store_data_in_duckdb(ticker, start, end, db_file="stockdata.duckdb")
data = get_data_from_duckdb(stat_dic[data_instruction], ticker, start, end)

try:
# Generate the plot
plot = data.plot.line(x="Date",
y=stat_dic[data_instruction],
color="Ticker",
facet_col="Ticker",
title=f"{' '.join(ticker)}: {data_instruction} from {start} to {end}")

# Display plot
visualization_area.object = plot
except Exception as e:
print(f"Error generating plot: {e}")


interpretation_area.object = "LLM is generating plot interpretation, please wait..."

# Save the plot
save_plot(plot, "plot_image.png")

result_url = save_image()

interpretation_text = analyze_image_with_text(result_url, question)

# Update the interpretation_area with the interpretation text
interpretation_area.object = interpretation_text

def submit_action(event):
submit_button.disabled = True
reset_button.disabled = True

update_visualization(ticker_input.value,
start_date.value,
end_date.value,
instruction_input.value,
question.value
)

submit_button.disabled = False
reset_button.disabled = False

def reset_action(event):
visualization_area.object = None
interpretation_area.objetc = None

# Define the stock symbols you're interested in for the dropdown
stock_symbols, symbol_name = get_stock_symbols()
stat = ["Closing price", "Opening price", "Highest value of day", "Lowest value of day"]
stat_dic = {"Closing price": "Close",
"Opening price": "Open",
"Highest value of day": "High",
"Lowest value of day": "Low"}
# Create a header with the app's title and description
header = pn.pane.Markdown("""
## LLM-powered NASDAQ Stock Analysis App
""", margin=(0, 0, 10, 0), align='center')

description = pn.pane.Markdown("""
### How does it work?
This app analyzes stock data and provides visualizations and interpretations.
1. Select a stock symbol from the dropdown.
2. Select a start and end date for the analysis.
3. Select the value you want to analyze (e.g., closing price, opening price, etc.).
4. Enter a natural language question about the stock data.
The app will then display a plot of the selected stock's value over time and provide an interpretation of the plot generated by an LLM that takes into account the user's question.
""", margin=(0, 0, 10, 0))

# Add a logo at the top of the user menu
logo = pn.pane.PNG('image.png', width=200, height=100, align='center')

# Add credits at the bottom of the user menu
credits = pn.pane.Markdown("""
# How it was built
* Data source: yfinance
* Data storage: DuckDB
* UI: Panel
* Plotting: Plotly
* Plot Interpretation: OpenAI's gpt-4-vision-preview
* Hosted on [Ploomber Cloud](https://ploomber.io/).
App Author: [Laura Funderburk](https://github.com/lfunderburk)
""", )


# UI Components for stock selection
# Define the AutocompleteInput for stock selection
ticker_input = pn.widgets.MultiChoice(
name='Stock Symbol',
options=stock_symbols,
value=['AAPL','GOOGL','AMZN']
)
start_date = pn.widgets.DatePicker(name='Start Date',
value=pd.to_datetime('2022-01-01'))
end_date = pn.widgets.DatePicker(name='End Date',
value=pd.to_datetime('2024-02-27'))
instruction_input = pn.widgets.Select(name='Value',
options = stat,
value='Close'
)
question = pn.widgets.TextAreaInput(name='Ask a natural language question',
height=90,
placeholder = "What stock displays strongest growth over the selected period?",
value = "What stock displays strongest growth over the selected period?")

# Visualization area where the plot will be displayed
visualization_area = pn.pane.Plotly()
interpretation_area = pn.pane.Markdown("", width=800)

submit_button = pn.widgets.Button(name='Submit', button_type='primary')
reset_button = pn.widgets.Button(name='Reset', button_type='danger')


submit_button.on_click(submit_action)
reset_button.on_click(reset_action)

# Organize the layout
user_menu = pn.Column(

header,
pn.pane.PNG('image.png', width=300, align='center'),
description,
ticker_input,
start_date,
end_date,
instruction_input,
question,
submit_button,
reset_button,
credits,
background='#F5F5F5',
width=300,
margin=(10, 10, 10, 10),
)

visualization_layout = pn.Column(
"# Visualization",
visualization_area,
interpretation_area,
background='#FFFFFF',
margin=(10, 10, 10, 10),
)

# Combine the menu and visualization layout into the main layout
main_layout = pn.Row(user_menu, visualization_layout)

# Serve the app with a custom template that hides the theme toggle
template = pn.template.FastListTemplate(
site="Ploomber Cloud",
title="AI-powered Stock Analysis App",
sidebar=[user_menu],
accent='#DAA520',
main=[visualization_layout],
theme_toggle=False,
)

# Servable without the theme toggle
template.servable()
33 changes: 33 additions & 0 deletions examples/panel/stock-market-chatbot/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from openai import OpenAI
import os
import pandas as pd


client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def analyze_image_with_text(image_url, text_query):
complete_question=f"You are an expert data analyst assistant specializing in reading plots. \
You will be presented with a plot that contains stock information. \
Please refer to each line using only the label, not the colour. \
Provide a high level overview summary that \
describes the trends in this plot. \
Your answer should be tailored towards \
the user question: {text_query}"
response = client.chat.completions.create(
model="gpt-4-vision-preview",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": complete_question},
{
"type": "image_url",
"image_url": {"url": image_url},
},
],
}
],
max_tokens=300,
)
return response.choices[0].message.content

Loading