Darshan03 commited on
Commit
7c12a4e
·
verified ·
1 Parent(s): c49a522

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +533 -0
app.py ADDED
@@ -0,0 +1,533 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import yfinance as yf
3
+ import pandas as pd
4
+ import numpy as np
5
+ import matplotlib.pyplot as plt
6
+ import ta
7
+ import os
8
+ from datetime import datetime
9
+ import google.generativeai as genai
10
+ from dotenv import load_dotenv
11
+ import markdown
12
+ import io
13
+ import base64
14
+ from xhtml2pdf import pisa
15
+ import logging
16
+ import json
17
+
18
+ # Load environment variables
19
+ load_dotenv()
20
+
21
+ # Configure logging
22
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
+
24
+ # Configuration
25
+ class Config:
26
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
27
+ OUTPUT_DIR = "output_files" # Base output directory
28
+
29
+ # Create output directories if they don't exist
30
+ if not os.path.exists(Config.OUTPUT_DIR):
31
+ os.makedirs(Config.OUTPUT_DIR)
32
+
33
+ # --------------------- Functions from technical_analysis.py ---------------------
34
+
35
+ def fetch_data(ticker, period="1y"):
36
+ """Fetches stock data from Yahoo Finance."""
37
+ logging.info(f"Fetching data for {ticker} for period {period}")
38
+ stock = yf.Ticker(ticker)
39
+ data = stock.history(period=period)
40
+ return data
41
+
42
+ def calculate_moving_averages(data, short_window=20, long_window=50):
43
+ """Calculates simple moving averages."""
44
+ logging.info("Calculating moving averages")
45
+ data['SMA_Short'] = data['Close'].rolling(window=short_window).mean()
46
+ data['SMA_Long'] = data['Close'].rolling(window=long_window).mean()
47
+ return data
48
+
49
+ def calculate_ema(data, window=20):
50
+ """Calculates exponential moving average."""
51
+ logging.info("Calculating EMA")
52
+ data['EMA'] = data['Close'].ewm(span=window, adjust=False).mean()
53
+ return data
54
+
55
+ def calculate_macd(data, short_window=12, long_window=26, signal_window=9):
56
+ """Calculates the MACD."""
57
+ logging.info("Calculating MACD")
58
+ macd = ta.trend.MACD(data['Close'], window_fast=short_window, window_slow=long_window, window_sign=signal_window)
59
+ data['MACD'] = macd.macd()
60
+ data['MACD_signal'] = macd.macd_signal()
61
+ data['MACD_histogram'] = macd.macd_diff()
62
+ return data
63
+
64
+ def calculate_rsi(data, window=14):
65
+ """Calculates the RSI."""
66
+ logging.info("Calculating RSI")
67
+ data['RSI'] = ta.momentum.RSIIndicator(data['Close'], window=window).rsi()
68
+ return data
69
+
70
+ def calculate_adx(data, window=14):
71
+ """Calculates the Average Directional Index (ADX)"""
72
+ logging.info("Calculating ADX")
73
+ adx = ta.trend.ADXIndicator(data['High'], data['Low'], data['Close'], window=window)
74
+ data['ADX'] = adx.adx()
75
+ data['ADX_pos'] = adx.adx_pos()
76
+ data['ADX_neg'] = adx.adx_neg()
77
+ return data
78
+
79
+ def calculate_atr(data, window=14):
80
+ """Calculates the ATR."""
81
+ logging.info("Calculating ATR")
82
+ data['ATR'] = ta.volatility.AverageTrueRange(data['High'], data['Low'], data['Close'], window=window).average_true_range()
83
+ return data
84
+
85
+ def calculate_bollinger_bands(data, window=20, window_dev=2):
86
+ """Calculates the Bollinger Bands."""
87
+ logging.info("Calculating Bollinger Bands")
88
+ bb = ta.volatility.BollingerBands(data['Close'], window=window, window_dev=window_dev)
89
+ data['BB_upper'] = bb.bollinger_hband()
90
+ data['BB_mid'] = bb.bollinger_mavg()
91
+ data['BB_lower'] = bb.bollinger_lband()
92
+ return data
93
+
94
+ def calculate_stochastic(data, window=14):
95
+ """Calculates the Stochastic Oscillator."""
96
+ logging.info("Calculating Stochastic Oscillator")
97
+ stoch = ta.momentum.StochasticOscillator(high=data['High'], low=data['Low'], close=data['Close'], window=window)
98
+ data['Stochastic_k'] = stoch.stoch()
99
+ data['Stochastic_d'] = stoch.stoch_signal()
100
+ return data
101
+
102
+ def print_indicator_outputs(data, ticker, output_dir):
103
+ """Prints key indicator values and explanations and save them in a file"""
104
+ logging.info(f"Generating analysis output for {ticker}")
105
+ output_text = f"----- {ticker} Technical Analysis Summary -----\n"
106
+
107
+ # Moving Averages:
108
+ if 'SMA_Short' in data.columns and 'SMA_Long' in data.columns:
109
+ latest_sma_short = data['SMA_Short'].iloc[-1]
110
+ latest_sma_long = data['SMA_Long'].iloc[-1]
111
+ output_text += "\n--- Moving Averages ---\n"
112
+ output_text += f" Latest Short-Term SMA ({data['SMA_Short'].name}): {latest_sma_short:.2f}\n"
113
+ output_text += f" Latest Long-Term SMA ({data['SMA_Long'].name}): {latest_sma_long:.2f}\n"
114
+
115
+ if latest_sma_short > latest_sma_long:
116
+ output_text += " Short-term SMA is above Long-term SMA: Potential uptrend signal.\n"
117
+ elif latest_sma_short < latest_sma_long:
118
+ output_text += " Short-term SMA is below Long-term SMA: Potential downtrend signal.\n"
119
+ else:
120
+ output_text += " Short and Long term SMAs are same. No clear trend signal from MA.\n"
121
+
122
+ if 'EMA' in data.columns:
123
+ latest_ema = data['EMA'].iloc[-1]
124
+ output_text += f" Latest Exponential Moving Average ({data['EMA'].name}): {latest_ema:.2f}\n"
125
+ if 'SMA_Short' in data.columns:
126
+ if latest_ema > latest_sma_short:
127
+ output_text += " Latest EMA is above short SMA: Potential uptrend signal\n"
128
+ elif latest_ema < latest_sma_short:
129
+ output_text += " Latest EMA is below short SMA: Potential downtrend signal\n"
130
+
131
+
132
+
133
+ # MACD
134
+ if 'MACD' in data.columns and 'MACD_signal' in data.columns and 'MACD_histogram' in data.columns:
135
+ latest_macd = data['MACD'].iloc[-1]
136
+ latest_signal = data['MACD_signal'].iloc[-1]
137
+ latest_hist = data['MACD_histogram'].iloc[-1]
138
+ output_text += "\n--- MACD ---\n"
139
+ output_text += f" Latest MACD: {latest_macd:.2f}\n"
140
+ output_text += f" Latest MACD Signal Line: {latest_signal:.2f}\n"
141
+ output_text += f" Latest MACD Histogram: {latest_hist:.2f}\n"
142
+
143
+ if latest_macd > latest_signal and latest_hist > 0:
144
+ output_text += " MACD is above signal line and histogram is positive: Potential bullish momentum.\n"
145
+ elif latest_macd < latest_signal and latest_hist < 0:
146
+ output_text += " MACD is below signal line and histogram is negative: Potential bearish momentum.\n"
147
+ elif latest_macd > latest_signal and latest_hist < 0:
148
+ output_text += " MACD is above signal line but histogram is negative: Potential weakening of bullish momentum\n"
149
+ elif latest_macd < latest_signal and latest_hist > 0:
150
+ output_text += " MACD is below signal line but histogram is positive: Potential weakening of bearish momentum\n"
151
+ else:
152
+ output_text += " No clear signal from MACD.\n"
153
+
154
+ # RSI
155
+ if 'RSI' in data.columns:
156
+ latest_rsi = data['RSI'].iloc[-1]
157
+ output_text += "\n--- RSI ---\n"
158
+ output_text += f" Latest RSI: {latest_rsi:.2f}\n"
159
+ if latest_rsi > 70:
160
+ output_text += " RSI is above 70: Overbought condition, potential pullback.\n"
161
+ elif latest_rsi < 30:
162
+ output_text += " RSI is below 30: Oversold condition, potential bounce.\n"
163
+ else:
164
+ output_text += " RSI is neither overbought nor oversold.\n"
165
+
166
+ # ADX
167
+ if 'ADX' in data.columns and 'ADX_pos' in data.columns and 'ADX_neg' in data.columns:
168
+ latest_adx = data['ADX'].iloc[-1]
169
+ latest_pos_di = data['ADX_pos'].iloc[-1]
170
+ latest_neg_di = data['ADX_neg'].iloc[-1]
171
+ output_text += "\n--- ADX ---\n"
172
+ output_text += f" Latest ADX: {latest_adx:.2f}\n"
173
+ output_text += f" Latest +DI: {latest_pos_di:.2f}\n"
174
+ output_text += f" Latest -DI: {latest_neg_di:.2f}\n"
175
+ if latest_adx > 25:
176
+ output_text += " ADX is above 25: Trend strength present.\n"
177
+ if latest_pos_di > latest_neg_di:
178
+ output_text += " +DI above -DI: Likely uptrend.\n"
179
+ elif latest_pos_di < latest_neg_di:
180
+ output_text += " -DI above +DI: Likely downtrend.\n"
181
+ else:
182
+ output_text += " ADX is below 25: Weak trend or no trend.\n"
183
+
184
+ # ATR
185
+ if 'ATR' in data.columns:
186
+ latest_atr = data['ATR'].iloc[-1]
187
+ output_text += "\n--- ATR ---\n"
188
+ output_text += f" Latest ATR: {latest_atr:.2f}\n"
189
+ output_text += f" High ATR indicates higher volatility, low ATR indicates lower volatility\n"
190
+
191
+ # Bollinger Bands
192
+ if 'BB_upper' in data.columns and 'BB_lower' in data.columns and 'BB_mid' in data.columns:
193
+ latest_close = data['Close'].iloc[-1]
194
+ latest_upper = data['BB_upper'].iloc[-1]
195
+ latest_lower = data['BB_lower'].iloc[-1]
196
+ latest_mid = data['BB_mid'].iloc[-1]
197
+ output_text += "\n--- Bollinger Bands ---\n"
198
+ output_text += f" Latest Close Price: {latest_close:.2f}\n"
199
+ output_text += f" Latest Upper Band: {latest_upper:.2f}\n"
200
+ output_text += f" Latest Middle Band: {latest_mid:.2f}\n"
201
+ output_text += f" Latest Lower Band: {latest_lower:.2f}\n"
202
+
203
+ if latest_close > latest_upper:
204
+ output_text += " Price is above the upper band: Potentially overbought.\n"
205
+ elif latest_close < latest_lower:
206
+ output_text += " Price is below the lower band: Potentially oversold.\n"
207
+ elif latest_close < latest_mid:
208
+ output_text += " Price is below the middle band: Potential downtrend\n"
209
+ elif latest_close > latest_mid:
210
+ output_text += " Price is above the middle band: Potential uptrend\n"
211
+ else:
212
+ output_text += " Price within Bollinger Bands\n"
213
+ # Stochastic Oscillator
214
+ if 'Stochastic_k' in data.columns and 'Stochastic_d' in data.columns:
215
+ latest_stoch_k = data['Stochastic_k'].iloc[-1]
216
+ latest_stoch_d = data['Stochastic_d'].iloc[-1]
217
+ output_text += "\n--- Stochastic Oscillator ---\n"
218
+ output_text += f" Latest Stochastic K: {latest_stoch_k:.2f}\n"
219
+ output_text += f" Latest Stochastic D: {latest_stoch_d:.2f}\n"
220
+
221
+ if latest_stoch_k > 80 and latest_stoch_d > 80:
222
+ output_text += " Both %K and %D above 80: Potentially overbought.\n"
223
+ elif latest_stoch_k < 20 and latest_stoch_d < 20:
224
+ output_text += " Both %K and %D below 20: Potentially oversold.\n"
225
+ elif latest_stoch_k > latest_stoch_d:
226
+ output_text += " %K crosses above %D : Potential bullish signal.\n"
227
+ elif latest_stoch_k < latest_stoch_d:
228
+ output_text += " %K crosses below %D : Potential bearish signal.\n"
229
+ else:
230
+ output_text += "No clear signal from stochastic\n"
231
+
232
+ # Save the text output
233
+ filename = os.path.join(output_dir, f"{ticker}_analysis.txt")
234
+ with open(filename, 'w') as f:
235
+ f.write(output_text)
236
+ logging.info(f"Saved analysis output to: {filename}")
237
+ return output_text
238
+
239
+ def plot_stock_and_indicators(data, ticker, output_dir):
240
+ """Plots the stock price and various indicators and saves the plot."""
241
+ logging.info(f"Plotting stock and indicators for {ticker}")
242
+ plt.figure(figsize=(15, 10))
243
+
244
+ # Subplot 1: Price and Moving Averages
245
+ plt.subplot(4, 1, 1)
246
+ plt.plot(data.index, data['Close'], label='Close Price', color='blue')
247
+ if 'SMA_Short' in data.columns:
248
+ plt.plot(data.index, data['SMA_Short'], label='SMA Short', color='orange')
249
+ if 'SMA_Long' in data.columns:
250
+ plt.plot(data.index, data['SMA_Long'], label='SMA Long', color='green')
251
+ if 'EMA' in data.columns:
252
+ plt.plot(data.index, data['EMA'], label='EMA', color='purple')
253
+ plt.title(f'{ticker} Price & Moving Averages')
254
+ plt.ylabel('Price')
255
+ plt.legend()
256
+ plt.grid(True)
257
+
258
+ # Subplot 2: MACD
259
+ plt.subplot(4, 1, 2)
260
+ if 'MACD' in data.columns:
261
+ plt.plot(data.index, data['MACD'], label='MACD', color='blue')
262
+ plt.plot(data.index, data['MACD_signal'], label='MACD Signal', color='orange')
263
+ plt.bar(data.index, data['MACD_histogram'], label='MACD Histogram', color='grey', alpha=0.6)
264
+ plt.axhline(0, color='black', linestyle='--', linewidth=0.7) # Zero line
265
+ plt.title('MACD')
266
+ plt.legend()
267
+ plt.grid(True)
268
+
269
+ # Subplot 3: RSI
270
+ plt.subplot(4, 1, 3)
271
+ if 'RSI' in data.columns:
272
+ plt.plot(data.index, data['RSI'], label='RSI', color='purple')
273
+ plt.axhline(70, color='red', linestyle='--', linewidth=0.7) # Overbought level
274
+ plt.axhline(30, color='green', linestyle='--', linewidth=0.7) # Oversold level
275
+ plt.title('RSI')
276
+ plt.ylabel('RSI Value')
277
+ plt.legend()
278
+ plt.grid(True)
279
+
280
+ # Subplot 4: ADX
281
+ plt.subplot(4, 1, 4)
282
+ if 'ADX' in data.columns:
283
+ plt.plot(data.index, data['ADX'], label='ADX', color='black')
284
+ plt.plot(data.index, data['ADX_pos'], label='+DI', color='green')
285
+ plt.plot(data.index, data['ADX_neg'], label='-DI', color='red')
286
+ plt.axhline(25, color='grey', linestyle='--', linewidth=0.7) # Threshold for strong trend
287
+ plt.title('ADX')
288
+ plt.ylabel('ADX Value')
289
+ plt.legend()
290
+ plt.grid(True)
291
+ plt.tight_layout()
292
+
293
+ # Save the plot
294
+ filename = os.path.join(output_dir, f"{ticker}_price_indicators.png")
295
+ plt.savefig(filename)
296
+ plt.close()
297
+ logging.info(f"Saved price plot to: {filename}")
298
+
299
+ def plot_volatility_indicators(data, ticker, output_dir):
300
+ """Plots the volatility indicators and save them."""
301
+ logging.info(f"Plotting volatility indicators for {ticker}")
302
+ plt.figure(figsize=(15, 10))
303
+
304
+ # Subplot 1: Price and Bollinger Bands
305
+ plt.subplot(3, 1, 1)
306
+ plt.plot(data.index, data['Close'], label='Close Price', color='blue')
307
+ if 'BB_upper' in data.columns:
308
+ plt.plot(data.index, data['BB_upper'], label='BB Upper', color='red')
309
+ plt.plot(data.index, data['BB_mid'], label='BB Mid', color='grey')
310
+ plt.plot(data.index, data['BB_lower'], label='BB Lower', color='green')
311
+ plt.title(f'{ticker} Price & Bollinger Bands')
312
+ plt.ylabel('Price')
313
+ plt.legend()
314
+ plt.grid(True)
315
+
316
+ # Subplot 2: ATR
317
+ plt.subplot(3, 1, 2)
318
+ if 'ATR' in data.columns:
319
+ plt.plot(data.index, data['ATR'], label='ATR', color='purple')
320
+ plt.title('ATR')
321
+ plt.ylabel('ATR Value')
322
+ plt.legend()
323
+ plt.grid(True)
324
+
325
+ #Subplot 3: Stochastic Oscillator
326
+ plt.subplot(3, 1, 3)
327
+ if 'Stochastic_k' in data.columns:
328
+ plt.plot(data.index, data['Stochastic_k'], label='%K', color='blue')
329
+ plt.plot(data.index, data['Stochastic_d'], label='%D', color='orange')
330
+ plt.axhline(80, color='red', linestyle='--', linewidth=0.7) # Overbought level
331
+ plt.axhline(20, color='green', linestyle='--', linewidth=0.7) # Oversold level
332
+ plt.title('Stochastic Oscillator')
333
+ plt.legend()
334
+ plt.grid(True)
335
+
336
+ plt.tight_layout()
337
+
338
+ # Save the plot
339
+ filename = os.path.join(output_dir, f"{ticker}_volatility_indicators.png")
340
+ plt.savefig(filename)
341
+ plt.close()
342
+ logging.info(f"Saved volatility plot to: {filename}")
343
+
344
+ def generate_prompt(analysis_text, image_paths):
345
+ """Creates a structured prompt for an LLM using analysis and image paths."""
346
+ logging.info("Generating LLM prompt")
347
+ prompt = f"""
348
+ Please analyze the following technical analysis of the stock, along with the related charts:
349
+
350
+ **Technical Analysis Text Output:**
351
+ {analysis_text}
352
+
353
+ **Image Paths:**
354
+ {image_paths}
355
+
356
+ Given this information, please provide the following:
357
+ - Summarize the overall technical outlook for this stock.
358
+ - Identify any significant patterns or signals.
359
+ - Suggest possible trading actions based on the analysis.
360
+ - Any additional insigths based on the analysis.
361
+ """
362
+ return prompt
363
+
364
+ def load_prompt(prompt_filepath):
365
+ """Loads the prompt from the given filepath and returns it."""
366
+ logging.info(f"Loading LLM prompt from {prompt_filepath}")
367
+ try:
368
+ with open(prompt_filepath, 'r') as f:
369
+ prompt = f.read()
370
+ return prompt
371
+ except FileNotFoundError:
372
+ logging.error(f"Error: Prompt file not found at {prompt_filepath}")
373
+ return None
374
+ except Exception as e:
375
+ logging.error(f"Error loading prompt: {e}")
376
+ return None
377
+
378
+ def get_response(llm, prompt):
379
+ """Generates a response from the LLM based on the provided prompt and context."""
380
+ logging.info("Sending prompt to LLM")
381
+ try:
382
+ response = llm.send_message(prompt)
383
+ logging.info("Received LLM response")
384
+ return response
385
+ except Exception as e:
386
+ logging.error(f"Error getting response from LLM: {e}")
387
+ return None
388
+
389
+ def markdown_to_pdf_xhtml2pdf(markdown_text, output_pdf_path):
390
+ """Converts markdown text to PDF using xhtml2pdf."""
391
+ logging.info(f"Converting markdown to PDF: {output_pdf_path}")
392
+ try:
393
+ html = markdown.markdown(markdown_text)
394
+ with open(output_pdf_path, "wb") as pdf_file:
395
+ pisa_status = pisa.CreatePDF(html, dest=pdf_file)
396
+ if pisa_status.err:
397
+ logging.error(f"Error converting to PDF: {pisa_status.err}")
398
+ else:
399
+ logging.info(f"PDF saved successfully to: {output_pdf_path}")
400
+ except Exception as e:
401
+ logging.error(f"Error converting to PDF: {e}")
402
+
403
+ def get_unique_output_dir(base_dir, ticker):
404
+ """Creates a unique output directory with a timestamp."""
405
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
406
+ output_dir = os.path.join(base_dir, f"output_{ticker}_{timestamp}")
407
+ os.makedirs(output_dir, exist_ok=True)
408
+ return output_dir
409
+
410
+ def get_stock_symbols(file_path):
411
+ """
412
+ Reads stock symbols from a JSON file.
413
+
414
+ Args:
415
+ file_path (str): The path to the JSON file.
416
+
417
+ Returns:
418
+ list: A list of stock symbols.
419
+ """
420
+ try:
421
+ with open(file_path, 'r') as f:
422
+ data = json.load(f)
423
+ symbols = [stock["symbol"] for stock in data.values()]
424
+ return symbols
425
+ except Exception as e:
426
+ st.error(f"Error reading stock symbols: {e}")
427
+ return []
428
+
429
+ # --------------------- Streamlit App ---------------------
430
+
431
+ def main():
432
+ st.title("Stock Technical Analysis with LLM")
433
+
434
+ # Input for stock data as a JSON file
435
+ st.header("1. Upload Stock Data (JSON)")
436
+ uploaded_file = st.file_uploader("Upload your stock_data.json file", type=["json"])
437
+ stock_symbols = []
438
+ if uploaded_file:
439
+ try:
440
+ stock_symbols = get_stock_symbols(uploaded_file)
441
+ if stock_symbols:
442
+ st.success("Stock data file uploaded successfully!")
443
+ else:
444
+ st.warning("No Stock Symbols found in the file")
445
+ except json.JSONDecodeError:
446
+ st.error("Invalid JSON format. Please upload a valid JSON file.")
447
+ except Exception as e:
448
+ st.error(f"An error occurred while processing the uploaded file: {e}")
449
+
450
+ # Dropdown to select the stock
451
+ selected_stock = st.selectbox("Select a stock for analysis:", stock_symbols)
452
+
453
+ # Period selection
454
+ period = st.selectbox("Select the time period for analysis:", ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"])
455
+
456
+ if st.button("Analyze"):
457
+ with st.spinner("Performing technical analysis..."):
458
+ try:
459
+ # Create a unique output directory
460
+ output_dir = get_unique_output_dir(Config.OUTPUT_DIR, selected_stock)
461
+
462
+ # Fetch and process data
463
+ stock_data = fetch_data(selected_stock, period=period)
464
+ stock_data = calculate_moving_averages(stock_data)
465
+ stock_data = calculate_ema(stock_data)
466
+ stock_data = calculate_macd(stock_data)
467
+ stock_data = calculate_rsi(stock_data)
468
+ stock_data = calculate_adx(stock_data)
469
+ stock_data = calculate_atr(stock_data)
470
+ stock_data = calculate_bollinger_bands(stock_data)
471
+ stock_data = calculate_stochastic(stock_data)
472
+
473
+ # Get analysis output
474
+ analysis_output = print_indicator_outputs(stock_data, selected_stock, output_dir)
475
+
476
+ # Plot and save charts
477
+ plot_stock_and_indicators(stock_data, selected_stock, output_dir)
478
+ plot_volatility_indicators(stock_data, selected_stock, output_dir)
479
+
480
+ # Generate prompt for LLM
481
+ image_paths = [os.path.join(output_dir, file) for file in os.listdir(output_dir) if file.endswith(('.png', '.jpg', '.jpeg'))]
482
+ image_paths = "\n".join(image_paths)
483
+ prompt = generate_prompt(analysis_output, image_paths)
484
+
485
+ # Save the prompt to a file
486
+ prompt_filename = os.path.join(output_dir, f"{selected_stock}_prompt.txt")
487
+ with open(prompt_filename, 'w') as f:
488
+ f.write(prompt)
489
+ logging.info(f"Saved LLM prompt to: {prompt_filename}")
490
+
491
+ # Configure and create LLM model
492
+ genai.configure(api_key=Config.GOOGLE_API_KEY)
493
+ generation_config = {
494
+ "temperature": 0.9,
495
+ "top_p": 0.95,
496
+ "top_k": 40,
497
+ "max_output_tokens": 8192,
498
+ }
499
+ model = genai.GenerativeModel(
500
+ model_name="gemini-pro", # Use "gemini-pro" for text-only
501
+ generation_config=generation_config,
502
+ )
503
+ chat_session = model.start_chat()
504
+
505
+ # Get LLM response
506
+ llm_response = get_response(chat_session, prompt)
507
+
508
+ if llm_response:
509
+ # Generate PDF
510
+ pdf_filename = os.path.join(output_dir, f"technical_analysis_{selected_stock}.pdf")
511
+ markdown_to_pdf_xhtml2pdf(llm_response.text, pdf_filename)
512
+
513
+ # Offer PDF for download
514
+ with open(pdf_filename, "rb") as pdf_file:
515
+ pdf_bytes = pdf_file.read()
516
+ st.download_button(
517
+ label="Download Analysis PDF",
518
+ data=pdf_bytes,
519
+ file_name=f"technical_analysis_{selected_stock}.pdf",
520
+ mime="application/pdf"
521
+ )
522
+ # Display the PDF using an iframe
523
+ b64 = base64.b64encode(pdf_bytes).decode()
524
+ pdf_display = f'<iframe src="data:application/pdf;base64,{b64}" width="700" height="1000" type="application/pdf"></iframe>'
525
+ st.markdown(pdf_display, unsafe_allow_html=True)
526
+ else:
527
+ st.error("Could not generate PDF. LLM response is empty.")
528
+
529
+ except Exception as e:
530
+ st.error(f"An error occurred during analysis: {e}")
531
+
532
+ if __name__ == "__main__":
533
+ main()