-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathstock.py
327 lines (280 loc) · 14.3 KB
/
stock.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
import streamlit as st
import pandas as pd
import yfinance as yf
import pandas_ta as ta
from datetime import datetime, timedelta
import time
import plotly.graph_objects as go
from groq import Groq
from search_engine import AISearch
aisearch = AISearch()
def load_stock_data(stock_data, intervals=['1d', '1h']):
summaries = {}
for interval in intervals:
if stock_data is not None and not stock_data.empty:
# Generate a summary for the last available data point
last_summary = stock_data.iloc[-1].to_dict()
summary_formatted = {f"{key}_{interval}": value for key, value in last_summary.items()}
summaries[interval] = summary_formatted
else:
summaries[interval] = {'error': f'No data available for {interval} interval.'}
return summaries
@st.cache_data(ttl=3600)
def fetch_and_calculate_indicators(symbol, interval):
# Adjust the start date based on the interval
end_date = datetime.today()
if interval == '1d':
start_date = end_date - timedelta(days=120) # 4 months for daily data
else:
# Adjust for hourly and 15-minute data to ensure within the 60-day limit
start_date = end_date - timedelta(days=60) # Adjusted for yfinance limitation
try:
# Fetch stock data using yfinance with the specified interval
stock_data = yf.download(symbol, start=start_date.strftime('%Y-%m-%d'), end=end_date.strftime('%Y-%m-%d'), interval=interval)
if stock_data.empty:
st.error(f"No data returned for {symbol} using interval {interval}.")
stock_data = None # Exit the function
# Calculate technical indicators using pandas-ta
stock_data.ta.macd(append=True)
stock_data.ta.rsi(append=True)
stock_data.ta.bbands(append=True)
stock_data.ta.obv(append=True)
stock_data.ta.sma(length=20, append=True)
stock_data.ta.ema(length=50, append=True)
stock_data.ta.stoch(append=True)
stock_data.ta.adx(append=True)
stock_data.ta.willr(append=True)
stock_data.ta.cmf(append=True)
stock_data.ta.psar(append=True)
# Convert OBV to million and handle MACD histogram naming
stock_data['OBV_in_million'] = stock_data['OBV'] / 1e6
stock_data['MACD_histogram_12_26_9'] = stock_data['MACDh_12_26_9']
except Exception as e:
print(f"Error fetching data for {symbol} at interval {interval}: {str(e)}")
stock_data = None # Ensure we return None to indicate failure
return stock_data
def create_score_cards(stock_data):
"""
Creates score cards for current price, RSI, and Volume
"""
if stock_data is not None and not stock_data.empty:
current_data = stock_data.iloc[-1]
previous_data = stock_data.iloc[-2]
# Calculate price change
price_change = current_data['Adj Close'] - previous_data['Adj Close']
price_change_percentage = (price_change / previous_data['Adj Close']) * 100
# Calculate volume change
volume_change = current_data['Volume'] - previous_data['Volume']
volume_change_percentage = (volume_change / previous_data['Volume']) * 100
# Calculate RSI change
rsi_change = current_data['RSI_14'] - previous_data['RSI_14']
return {
'price': {
'current': current_data['Adj Close'],
'change': price_change,
'change_percentage': price_change_percentage
},
'volume': {
'current': current_data['Volume'],
'change': volume_change,
'change_percentage': volume_change_percentage
},
'rsi': {
'current': current_data['RSI_14'],
'change': rsi_change
}
}
return None
@st.cache_data(ttl=3600,show_spinner=False)
def create_chart_for_indicator(stock_data, indicator_name, title, legend=True, hlines=None):
"""
Creates an interactive figure for a specific stock indicator for displaying in Streamlit using Plotly.
Parameters:
- stock_data: DataFrame containing the stock data and indicators
- indicator_name: list of column names in stock_data to plot
- title: str, the title of the chart
- legend: bool, whether to display the legend
- hlines: list of tuples (y, color, dash_style) for horizontal lines
Returns:
- fig: The Plotly figure object to be displayed in Streamlit.
"""
fig = go.Figure()
# Plot each indicator as a separate line
for name in indicator_name:
fig.add_trace(go.Scatter(x=stock_data.index, y=stock_data[name], mode='lines', name=name))
# Add horizontal lines if specified
if hlines:
for hline in hlines:
# Ensure the dash style is one of Plotly's accepted values
# For a dashed line similar to Matplotlib's '--', use 'dash'
dash_style = hline[2] if hline[2] in ['solid', 'dot', 'dash', 'longdash', 'dashdot', 'longdashdot'] else 'solid'
fig.add_shape(type="line",
x0=stock_data.index.min(),
y0=hline[0],
x1=stock_data.index.max(),
y1=hline[0],
line=dict(color=hline[1], dash=dash_style),
)
# Optionally add annotation next to the line
fig.add_annotation(x=stock_data.index.mean(), y=hline[0], text=f"{hline[0]}", showarrow=False)
fig.update_layout(title=title, xaxis_title="Date", yaxis_title="Value", legend_title="Indicator", height=400)
fig.update_xaxes(tickangle=-45)
return fig
@st.cache_data(ttl=3600,show_spinner=False)
def create_separate_charts(stock_data):
"""
Generates separate charts for key stock indicators and displays them in two columns in Streamlit.
Parameters:
- stock_data: DataFrame containing the stock data and indicators
"""
# Define configurations for different charts
charts_config = [
{'indicator_name': ['Adj Close', 'EMA_50', 'SMA_20'], 'title': 'Price Trend'},
{'indicator_name': ['OBV'], 'title': 'On-Balance Volume (OBV) Indicator'},
{'indicator_name': ['MACD_12_26_9', 'MACDh_12_26_9'], 'title': 'MACD Indicator'},
{'indicator_name': ['RSI_14'], 'title': 'RSI Indicator', 'hlines': [(70, 'red', '--'), (30, 'green', '--')]},
{'indicator_name': ['BBU_5_2.0', 'BBM_5_2.0', 'BBL_5_2.0', 'Adj Close'], 'title': 'Bollinger Bands'},
{'indicator_name': ['STOCHk_14_3_3', 'STOCHd_14_3_3'], 'title': 'Stochastic Oscillator'},
{'indicator_name': ['WILLR_14'], 'title': 'Williams %R'},
{'indicator_name': ['ADX_14'], 'title': 'Average Directional Index (ADX)'},
{'indicator_name': ['CMF_20'], 'title': 'Chaikin Money Flow (CMF)'}
]
# Generate and display each chart directly in the app
for config in charts_config:
fig = create_chart_for_indicator(
stock_data=stock_data,
indicator_name=config['indicator_name'],
title=config['title'],
legend=True,
hlines=config.get('hlines', None)
)
st.plotly_chart(fig, use_container_width=True)
@st.cache_data(ttl=3600,show_spinner=False)
def run_openai(timeframe,symbol,last_day_summary):
st.session_state.ai_key = st.secrets["GROQ_API_KEY"]
client = Groq(api_key=st.session_state.ai_key)
latest_news = aisearch.serch_prompt_generate(symbol,search_mode=True)
system_prompt = f"""
Assume the role as a leading Technical Analysis (TA) expert in the stock/crypto market.
Your mastery encompasses both stock and crypto fundamentals and intricate technical indicators.
You possess the ability to decode complex market dynamics,
providing clear insights and recommendations backed by a thorough understanding of interrelated factors.
Your expertise extends to practical tools like the pandas_ta module,
allowing you to navigate data intricacies with ease.
Use this latest news as a reference in decision. {latest_news}.
As a TA authority, your role is to decipher market trends, make informed predictions, and offer valuable perspectives.
Answer the following in a simple term and markdown format: Stock /Crypto Name, Executive Summary,Technical Analysis,Investment Strategies,News Summary. Use Makrdown h3 Tags for titles.
1. What this given stock symbol represents?
2. Write overall executive summary for direction and predictive movement.
2. Given TA data as below on the last trading {timeframe}, what will be the next few {timeframe} possible stock price movement?
3. Give me idea as both long term investment and short term trading.
5. Share summary of latest news on this ticker along with reference.
"""
response = client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"""The token is {symbol} ,\nHere is the summary indicators.\n{last_day_summary}"""}
],
max_tokens=1000
)
ai_response = response.choices[0].message.content
return ai_response
def format_number(number):
"""Helper function to format numbers for display"""
if abs(number) >= 1_000_000:
return f"{number/1_000_000:.2f}M"
elif abs(number) >= 1_000:
return f"{number/1_000:.2f}K"
else:
return f"{number:.2f}"
def streamlit_app():
st.set_page_config(layout="wide")
col1,col2,col3 = st.columns([1,3,1])
with col1:
st.image('stocklyzer.png',width=150)
with col2:
st.title("StockLyzer")
st.text("Here you can view the stock data and make predictions")
with col3:
st.image("qr_code.png", width=100)
st.markdown("**Some information on this page is AI-generated. This app is developed for educational purposes only and is not advisable to rely on it for financial decision-making.**")
symbols = ['AAPL','ADBE','ADA-USD', 'AMD', 'ARB11841-USD', 'AVAX-USD', 'BNB-USD', 'BTC-USD', 'COIN', 'DOGE-USD', 'ETH-USD', 'GOOGL', 'ICP-USD', 'INTC', 'KSM-USD', 'LINK-USD', 'MATIC-USD', 'MSTR', 'MSFT', 'NEAR-USD', 'NVDA', 'SEI-USD', 'SOL-USD', 'TSLA', 'TSM','Custom symbol...']
col1, col2, col3 = st.columns([4, 4, 2])
with col1:
with st.container():
selected_symbol = st.selectbox("Select a Symbol", symbols)
# Check if the user selected the option to enter a custom symbol
if selected_symbol == 'Custom symbol...':
custom_symbol = st.text_input("Enter Custom Symbol")
# Use the custom symbol if provided, otherwise keep showing the input field
if custom_symbol:
selected_symbol = custom_symbol
with col2:
interval_options = {'Daily': '1d', 'Hourly': '1h'}
selected_interval = st.selectbox("Choose Interval", list(interval_options.keys()), format_func=lambda x: x)
interval_value = interval_options[selected_interval]
data_loaded = False
with col3:
st.text("")
st.text("")
if st.button('Predict', key='load_data'):
data_loaded = True
if data_loaded:
with st.spinner(f'Loading data for {selected_symbol}...'):
stock_data = fetch_and_calculate_indicators(selected_symbol, interval_value)
if stock_data is not None:
summaries = load_stock_data(stock_data, intervals=[interval_value])
score_cards = create_score_cards(stock_data)
# Display score cards in three columns
if score_cards:
st.markdown("### Key Metrics")
metric_col1, metric_col2, metric_col3 = st.columns(3)
with metric_col1:
st.metric(
"Current Price",
f"${score_cards['price']['current']:.2f}",
f"{score_cards['price']['change_percentage']:.2f}%"
)
with metric_col2:
st.metric(
"RSI (14)",
f"{score_cards['rsi']['current']:.2f}",
f"{score_cards['rsi']['change']:.2f}"
)
with metric_col3:
st.metric(
"Volume",
format_number(score_cards['volume']['current']),
f"{score_cards['volume']['change_percentage']:.2f}%"
)
textcol, chartcol = st.columns([4, 6])
with textcol:
if stock_data is not None:
ai_response = run_openai(interval_value, selected_symbol, summaries)
st.markdown(f"## \n\n{ai_response}")
st.markdown("**This analysis has been generated using AI and is intended solely for educational purposes. It is not advisable to rely on it for financial decision-making.**")
st.markdown(f"### **Summary for {interval_value} interval**")
with chartcol:
if stock_data is not None:
st.markdown(f"## **Stock Data for {selected_symbol}**")
create_separate_charts(stock_data)
st.write("---")
# Buy Me a Coffee Button
st.markdown(
"""
## Enjoying this app?
If you find this app helpful, consider buying me a coffee! ☕️
<a href="https://www.buymeacoffee.com/nyeinchankoko" target="_blank">
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png"
alt="Buy Me A Coffee"
style="height: 60px !important; width: 217px !important;">
</a>
""",
unsafe_allow_html=True
)
st.write("#### About This App")
st.write("""This app demonstrates how to analyze custom CSV data for currency exchange rates, calculate technical indicators, and display the data and indicators using Plotly in Streamlit.
\nNyein Chan Ko Ko [nchanko](https://github.com/nchanko)""")
if __name__ == '__main__':
streamlit_app()