Anupam251272 commited on
Commit
5818583
·
verified ·
1 Parent(s): b892c7f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +352 -0
app.py ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Install required packages
2
+ # !pip install gradio yfinance beautifulsoup4 requests pandas numpy transformers xgboost scikit-learn python-dotenv
3
+ # !pip install python-decouple
4
+ import gradio as gr
5
+ import yfinance as yf
6
+ import requests
7
+ from bs4 import BeautifulSoup
8
+ import pandas as pd
9
+ import numpy as np
10
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
11
+ import xgboost as xgb
12
+ from datetime import datetime, timedelta
13
+ import json
14
+ import warnings
15
+ import os
16
+ from dotenv import load_dotenv
17
+ from decouple import config
18
+ import logging
19
+
20
+
21
+ warnings.filterwarnings('ignore')
22
+ load_dotenv()
23
+
24
+ # Configure logging
25
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
26
+
27
+ # WhatsApp API Configuration
28
+ WHATSAPP_API_BASE_URL = os.getenv('WHATSAPP_API_BASE_URL')
29
+ WHATSAPP_API_KEY = os.getenv('WHATSAPP_API_KEY')
30
+ WHATSAPP_INSTANCE_NAME = os.getenv('WHATSAPP_INSTANCE_NAME')
31
+
32
+ class ConfigManager:
33
+ """
34
+ Centralized configuration management
35
+ """
36
+ @staticmethod
37
+ def get_api_config():
38
+ """
39
+ Retrieve API configurations securely
40
+ """
41
+ return {
42
+ 'amfi_base_url': config('AMFI_API_URL', default='https://api.mfapi.in/mf'),
43
+ }
44
+
45
+ class WhatsAppManager:
46
+ def __init__(self, base_url, api_key):
47
+ self.base_url = base_url
48
+ self.api_key = api_key
49
+
50
+ def send_message(self, instance, phone, message):
51
+ if not self.base_url or not self.api_key or not instance:
52
+ logging.error("WhatsApp API base URL, key or instance not configured.")
53
+ return "WhatsApp API base URL, key or instance not configured."
54
+
55
+ headers = {
56
+ 'Content-Type': 'application/json',
57
+ 'Authorization': self.api_key
58
+ }
59
+ payload = json.dumps({
60
+ "phone": phone,
61
+ "message": message
62
+ })
63
+ try:
64
+ response = requests.post(f"{self.base_url}/message/sendText/{instance}", headers=headers, data=payload)
65
+ response.raise_for_status()
66
+ return response.json()
67
+ except requests.exceptions.RequestException as e:
68
+ logging.error(f"Error sending WhatsApp message: {str(e)}")
69
+ return f"Error sending message: {str(e)}"
70
+
71
+
72
+ class AMFIApi:
73
+ """
74
+ Mutual Fund API Handler with real-time data fetching
75
+ """
76
+ @staticmethod
77
+ def get_all_mutual_funds():
78
+ """
79
+ Retrieve comprehensive mutual funds list from AMFI API
80
+ """
81
+ config = ConfigManager.get_api_config()
82
+ try:
83
+ response = requests.get(config['amfi_base_url'])
84
+ if response.status_code == 200:
85
+ return response.json()
86
+ else:
87
+ logging.error("Failed to fetch mutual fund data.")
88
+ return "Error fetching mutual funds."
89
+ except Exception as e:
90
+ logging.error(f"API request error: {str(e)}")
91
+ return f"Error fetching mutual funds: {str(e)}"
92
+
93
+ @staticmethod
94
+ def analyze_mutual_fund(scheme_code):
95
+ """
96
+ Fetch real-time mutual fund NAV and analyze returns.
97
+ """
98
+ try:
99
+ config = ConfigManager.get_api_config()
100
+ amfi_url = f"{config['amfi_base_url']}/{scheme_code}"
101
+ response = requests.get(amfi_url)
102
+
103
+ if response.status_code != 200:
104
+ logging.error("Failed to fetch NAV data.")
105
+ return None, None, "Failed to fetch live NAV data."
106
+
107
+ fund_data = response.json()
108
+ if 'data' not in fund_data:
109
+ logging.error("Invalid fund data received.")
110
+ return None, None, "Invalid fund data received."
111
+
112
+ nav_data = pd.DataFrame(fund_data['data'])
113
+
114
+ # Data type conversions
115
+ nav_data['date'] = pd.to_datetime(nav_data['date'], format='%d-%m-%Y')
116
+ nav_data['nav'] = pd.to_numeric(nav_data['nav'], errors='coerce')
117
+
118
+ # Sort by date
119
+ nav_data = nav_data.sort_values('date')
120
+
121
+ # Calculate returns
122
+ latest_nav = nav_data.iloc[-1]['nav']
123
+ first_nav = nav_data.iloc[0]['nav']
124
+
125
+ returns = {
126
+ 'scheme_name': fund_data.get('meta', {}).get('scheme_name', 'Unknown'),
127
+ 'current_nav': latest_nav,
128
+ 'initial_nav': first_nav,
129
+ 'total_return': ((latest_nav - first_nav) / first_nav) * 100
130
+ }
131
+
132
+ return returns, nav_data[['date', 'nav']].rename(columns={'nav': 'NAV'}), None
133
+
134
+ except Exception as e:
135
+ logging.error(f"Analysis error: {str(e)}")
136
+ return None, None, f"Analysis error: {str(e)}"
137
+
138
+
139
+ def get_stock_data(symbol, period='3y'):
140
+ try:
141
+ logging.info(f"Fetching stock data for symbol: {symbol}")
142
+
143
+ stock = yf.Ticker(symbol)
144
+ logging.info(f"Ticker object created successfully for symbol: {symbol}")
145
+ hist = stock.history(period=period)
146
+ if hist.empty:
147
+ logging.error(f"No stock data available for symbol: {symbol} after fetching history.")
148
+ return f"No stock data available for symbol: {symbol}"
149
+ logging.info(f"Successfully fetched stock data for symbol: {symbol}")
150
+ return hist
151
+ except Exception as e:
152
+ logging.error(f"Error fetching stock data for symbol: {symbol}, error: {str(e)}")
153
+ return f"Error fetching stock data: {str(e)}"
154
+
155
+ def calculate_rsi(data, periods=14):
156
+ delta = data.diff()
157
+ gain = (delta.where(delta > 0, 0)).rolling(window=periods).mean()
158
+ loss = (-delta.where(delta < 0, 0)).rolling(window=periods).mean()
159
+ rs = gain / loss
160
+ return 100 - (100 / (1 + rs))
161
+
162
+ def predict_stock(symbol):
163
+ df = get_stock_data(symbol)
164
+ if isinstance(df, str):
165
+ return df
166
+
167
+ logging.info(f"Dataframe before feature creation for {symbol}: \n{df.head()}")
168
+
169
+ df['SMA_20'] = df['Close'].rolling(window=20).mean()
170
+ df['SMA_50'] = df['Close'].rolling(window=50).mean()
171
+ df['RSI'] = calculate_rsi(df['Close'])
172
+
173
+ features = ['SMA_20', 'SMA_50', 'RSI', 'Volume']
174
+ X = df[features].dropna()
175
+ y = df['Close'].shift(-1).dropna()
176
+
177
+ logging.info(f"Dataframe after feature creation: \nX:\n{X.head()}\ny:\n{y.head()}")
178
+
179
+ #Align X and Y
180
+ X = X.iloc[:len(y)]
181
+
182
+ split = int(len(X) * 0.8)
183
+ X_train, X_test = X[:split], X[split:]
184
+ y_train, y_test = y[:split], y[split:]
185
+
186
+
187
+ logging.info(f"Data split details: \nTrain Data size: {len(X_train)}\nTest Data Size: {len(X_test)}")
188
+
189
+ if len(X_train) == 0 or len(y_train) == 0:
190
+ logging.error(f"Insufficient training data for prediction for symbol: {symbol}")
191
+ return "Insufficient data for prediction."
192
+
193
+ model = xgb.XGBRegressor(objective='reg:squarederror', n_estimators=100)
194
+ model.fit(X_train, y_train)
195
+
196
+ if not X_test.empty:
197
+ last_data = X_test.iloc[-1:]
198
+ prediction = model.predict(last_data)[0]
199
+ return prediction
200
+ else:
201
+ logging.warning(f"No test data available for prediction for symbol: {symbol}")
202
+ return "No test data available for prediction."
203
+
204
+ def analyze_sentiment(text):
205
+ tokenizer = AutoTokenizer.from_pretrained("ProsusAI/finbert")
206
+ model = AutoModelForSequenceClassification.from_pretrained("ProsusAI/finbert")
207
+
208
+ inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True)
209
+ outputs = model(**inputs)
210
+ predictions = outputs.logits.softmax(dim=-1)
211
+
212
+ labels = ['negative', 'neutral', 'positive']
213
+ return {label: float(pred) for label, pred in zip(labels, predictions[0])}
214
+
215
+ def setup_notifications(phone, stock, mf, sentiment):
216
+ if not WHATSAPP_API_BASE_URL or not WHATSAPP_API_KEY or not WHATSAPP_INSTANCE_NAME:
217
+ return "WhatsApp API credentials or instance missing."
218
+
219
+ whatsapp_manager = WhatsAppManager(WHATSAPP_API_BASE_URL, WHATSAPP_API_KEY)
220
+
221
+ try:
222
+ result = whatsapp_manager.send_message(
223
+ WHATSAPP_INSTANCE_NAME,
224
+ phone,
225
+ "🎉 Welcome to AI Finance Manager!\nYour WhatsApp notifications have been set up successfully."
226
+ )
227
+
228
+ alerts = []
229
+ if stock: alerts.append("Stock")
230
+ if mf: alerts.append("Mutual Fund")
231
+ if sentiment: alerts.append("Sentiment")
232
+
233
+ return f"WhatsApp notifications set up for: {', '.join(alerts)} - {result}"
234
+ except Exception as e:
235
+ logging.error(f"Error setting up notifications: {str(e)}")
236
+ return f"Error setting up notifications: {str(e)}"
237
+
238
+ # Chatbot Function
239
+ def chatbot_response(user_input):
240
+ user_input = user_input.lower()
241
+
242
+ if "stock" in user_input:
243
+ parts = user_input.split()
244
+ if len(parts) > 1:
245
+ symbol = parts[-1].upper()
246
+ prediction = predict_stock(symbol)
247
+ if isinstance(prediction,str):
248
+ return prediction
249
+ else:
250
+ return f"The predicted next-day closing price for {symbol} is {prediction:.2f}"
251
+ else:
252
+ return "Please provide a stock symbol."
253
+
254
+
255
+ elif "mutual fund" in user_input:
256
+ parts = user_input.split()
257
+ if len(parts) > 2 and parts[1] == "code":
258
+ scheme_code = parts[-1]
259
+ mf_returns, mf_nav_history, error = AMFIApi.analyze_mutual_fund(scheme_code)
260
+ if error:
261
+ return error
262
+ else:
263
+ return f"Mutual Fund Analysis:\nName: {mf_returns.get('scheme_name', 'Unknown')}\nCurrent NAV: {mf_returns.get('current_nav', 'N/A'):.2f}\nTotal Return: {mf_returns.get('total_return', 'N/A'):.2f}%"
264
+ else:
265
+ return "Please enter the mutual fund scheme code for analysis (e.g. 'analyze mutual fund code 123456')."
266
+ elif "sentiment" in user_input:
267
+ return "Enter the financial news text for sentiment analysis."
268
+ elif user_input.startswith("analyze sentiment"):
269
+ text = user_input[len("analyze sentiment"):].strip()
270
+ if text:
271
+ sentiment_result = analyze_sentiment(text)
272
+ if sentiment_result:
273
+ return f"Sentiment Analysis: {sentiment_result}"
274
+ else:
275
+ return "No text provided for sentiment analysis."
276
+ else:
277
+ return "Please provide text for sentiment analysis."
278
+ return "I can help with Stock Analysis, Mutual Funds, and Sentiment Analysis. Please ask your query."
279
+
280
+
281
+ # Create Gradio Interface
282
+ def create_gradio_interface():
283
+ with gr.Blocks() as app:
284
+ gr.Markdown("# AI Finance & Stock Manager with Chat and WhatsApp Alerts")
285
+
286
+ with gr.Tab("Chat"):
287
+ chat_input = gr.Textbox(label="Ask about Stocks, Mutual Funds, or Sentiment Analysis")
288
+ chat_output = gr.Textbox(label="AI Response", interactive=False)
289
+ chat_btn = gr.Button("Ask AI")
290
+
291
+ with gr.Tab("Stock Analysis"):
292
+ stock_input = gr.Textbox(label="Enter Stock Symbol (e.g., AAPL)")
293
+ stock_btn = gr.Button("Analyze Stock")
294
+ stock_output = gr.DataFrame()
295
+ prediction_output = gr.Number(label="Predicted Next Day Close Price")
296
+
297
+ with gr.Tab("Mutual Fund Analysis"):
298
+ mf_code = gr.Textbox(label="Enter Scheme Code")
299
+ mf_analyze_btn = gr.Button("Analyze Fund")
300
+
301
+ # Analysis Outputs
302
+ mf_returns = gr.JSON(label="Fund Returns")
303
+ mf_nav_history = gr.DataFrame(label="NAV History")
304
+ mf_analysis_error = gr.Textbox(label="Error Messages", visible=False)
305
+
306
+ with gr.Tab("WhatsApp Notifications"):
307
+ phone_input = gr.Textbox(label="WhatsApp Number (with country code)")
308
+ enable_stock_alerts = gr.Checkbox(label="Stock Alerts")
309
+ enable_mf_alerts = gr.Checkbox(label="Mutual Fund Alerts")
310
+ enable_sentiment_alerts = gr.Checkbox(label="Sentiment Alerts")
311
+ notification_status = gr.Textbox(label="Notification Status", interactive=False)
312
+ setup_btn = gr.Button("Setup WhatsApp Notifications")
313
+
314
+ with gr.Tab("Sentiment Analysis"):
315
+ text_input = gr.Textbox(label="Enter financial news or text")
316
+ sentiment_btn = gr.Button("Analyze Sentiment")
317
+ sentiment_output = gr.Label()
318
+ # Event Handlers
319
+ chat_btn.click(
320
+ fn=chatbot_response,
321
+ inputs=chat_input,
322
+ outputs=chat_output
323
+ )
324
+
325
+ stock_btn.click(
326
+ fn=lambda x: (get_stock_data(x), predict_stock(x)),
327
+ inputs=stock_input,
328
+ outputs=[stock_output, prediction_output]
329
+ )
330
+ mf_analyze_btn.click(
331
+ fn=AMFIApi.analyze_mutual_fund,
332
+ inputs=mf_code,
333
+ outputs=[mf_returns,mf_nav_history,mf_analysis_error]
334
+ )
335
+
336
+ sentiment_btn.click(
337
+ fn=analyze_sentiment,
338
+ inputs=text_input,
339
+ outputs=sentiment_output
340
+ )
341
+ setup_btn.click(
342
+ fn=setup_notifications,
343
+ inputs=[phone_input, enable_stock_alerts, enable_mf_alerts, enable_sentiment_alerts],
344
+ outputs=notification_status
345
+ )
346
+ return app
347
+
348
+
349
+ # Launch the app
350
+ if __name__ == "__main__":
351
+ app = create_gradio_interface()
352
+ app.launch(share=True, debug=True)