Toymakerftw commited on
Commit
41fda59
·
1 Parent(s): 0d0cec0
Files changed (2) hide show
  1. app.py +212 -178
  2. requirements.txt +3 -1
app.py CHANGED
@@ -2,12 +2,15 @@ import logging
2
  import gradio as gr
3
  import pandas as pd
4
  import torch
 
5
  from GoogleNews import GoogleNews
6
  from transformers import pipeline
7
  import yfinance as yf
8
  import requests
9
  from fuzzywuzzy import process
10
  import statistics
 
 
11
 
12
  # Set up logging
13
  logging.basicConfig(
@@ -27,15 +30,94 @@ sentiment_analyzer = pipeline(
27
  )
28
  logging.info("Model initialized successfully")
29
 
30
- # Exchange suffixes for Yahoo Finance
 
 
 
 
 
 
 
 
 
 
 
31
  EXCHANGE_SUFFIXES = {
32
  "NSE": ".NS",
33
  "BSE": ".BO",
34
  "NYSE": "",
35
  "NASDAQ": "",
36
- # Add more exchanges as needed
37
  }
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  def resolve_ticker_symbol(query: str, exchange: str = "NSE") -> str:
40
  """
41
  Convert company names/partial symbols to valid Yahoo Finance tickers.
@@ -96,64 +178,57 @@ def analyze_article_sentiment(article):
96
  return article
97
 
98
  def fetch_yfinance_data(ticker):
99
- """Enhanced Yahoo Finance data fetching using official yfinance patterns"""
100
  try:
101
  logging.info(f"Fetching Yahoo Finance data for: {ticker}")
102
  stock = yf.Ticker(ticker)
103
 
104
- # Get market state and current session prices
105
- info = stock.info
106
- history = stock.history(period="2d", interval="1d") # Get 2 days for change calculation
107
 
108
- # Base price information
109
- current_price = info.get('currentPrice',
110
- info.get('regularMarketPrice',
111
- info.get('ask', 'N/A')))
 
 
 
 
 
 
 
112
 
113
- # Calculate price change using proper market session data
114
- if not history.empty and len(history) > 1:
115
- prev_close = history.iloc[-2]['Close']
116
- current_close = history.iloc[-1]['Close']
117
- change = current_close - prev_close
118
- percent_change = (change / prev_close) * 100
119
- else:
120
- change = info.get('regularMarketChange', 'N/A')
121
- percent_change = info.get('regularMarketChangePercent', 'N/A')
122
-
123
- # Format market cap with proper suffixes
124
- market_cap = info.get('marketCap')
125
- if market_cap and market_cap != 'N/A':
126
- market_cap = f"${_format_number(market_cap)}"
127
-
128
  return {
129
- # Core pricing
130
- 'price': f"{current_price:.2f}" if isinstance(current_price, float) else current_price,
131
- 'currency': info.get('currency', 'USD'),
132
- 'previous_close': f"{prev_close:.2f}" if 'prev_close' in locals() else 'N/A',
133
-
134
- # Price movements
135
- 'change': f"{change:.2f}" if isinstance(change, float) else change,
136
- 'percent_change': f"{percent_change:.2f}%" if isinstance(percent_change, float) else percent_change,
137
- 'day_range': f"{info.get('dayLow', 'N/A')} - {info.get('dayHigh', 'N/A')}",
138
-
139
- # Market data
140
- 'market_cap': market_cap,
141
- 'volume': _format_number(info.get('volume', 'N/A')),
142
- 'pe_ratio': info.get('trailingPE', info.get('forwardPE', 'N/A')),
143
- '52_week_range': f"{info.get('fiftyTwoWeekLow', 'N/A')} - {info.get('fiftyTwoWeekHigh', 'N/A')}",
144
-
145
- # Additional fundamentals
146
- 'dividend_yield': f"{info.get('dividendYield', 0) * 100:.2f}%" if info.get('dividendYield') else 'N/A',
147
- 'beta': info.get('beta', 'N/A'),
148
-
149
- # Market state
150
- 'market_state': info.get('marketState', 'CLOSED').title(),
151
- 'exchange': info.get('exchangeName', 'N/A')
152
  }
153
 
154
  except Exception as e:
155
  logging.error(f"Error fetching Yahoo Finance data: {str(e)}")
156
- return {"error": f"Failed to fetch data for {ticker}: {str(e)}"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  def _format_number(num):
159
  """Helper to format large numbers with suffixes"""
@@ -187,86 +262,60 @@ def convert_to_dataframe(analyzed_articles):
187
  return df[["Sentiment", "Title", "Description", "Date"]]
188
 
189
  def generate_stock_recommendation(articles, finance_data):
190
- """
191
- Generate a stock recommendation based on sentiment analysis and financial indicators
192
- """
193
- # Extract sentiment from articles
194
- if not articles or len(articles) == 0:
195
- return "Insufficient data for recommendation"
196
-
197
- # Sentiment scoring
198
- sentiment_labels = [article['sentiment']['label'] for article in articles]
199
- sentiment_scores = {
200
- 'positive': sentiment_labels.count('positive'),
201
- 'negative': sentiment_labels.count('negative'),
202
- 'neutral': sentiment_labels.count('neutral')
203
- }
204
-
205
- # Calculate sentiment ratio
206
- total_articles = len(sentiment_labels)
207
- sentiment_ratio = {
208
- 'positive': sentiment_scores['positive'] / total_articles * 100,
209
- 'negative': sentiment_scores['negative'] / total_articles * 100,
210
- 'neutral': sentiment_scores['neutral'] / total_articles * 100
211
- }
212
 
213
- # Recommendation logic
214
- recommendation = {
 
215
  'recommendation': 'HOLD',
216
  'confidence': 'Medium',
217
- 'reasons': []
 
218
  }
219
 
220
- # Financial indicators
221
- price_change = finance_data.get('percent_change', 0)
222
- pe_ratio = finance_data.get('pe_ratio', 'N/A')
223
- dividend_yield = finance_data.get('dividend_yield', 'N/A')
224
-
225
- # Sentiment-based recommendation
226
- if sentiment_ratio['positive'] > 60:
227
- recommendation['recommendation'] = 'BUY'
228
- recommendation['confidence'] = 'High'
229
- recommendation['reasons'].append("Predominantly positive news sentiment")
230
- elif sentiment_ratio['negative'] > 60:
231
- recommendation['recommendation'] = 'SELL'
232
- recommendation['confidence'] = 'High'
233
- recommendation['reasons'].append("Predominantly negative news sentiment")
234
-
235
- # Additional financial considerations
236
- if isinstance(price_change, float):
237
- if price_change > 2:
238
- recommendation['reasons'].append(f"Strong positive price movement (+{price_change:.2f}%)")
239
- elif price_change < -2:
240
- recommendation['reasons'].append(f"Significant price decline ({price_change:.2f}%)")
241
-
242
- # PE Ratio consideration
243
- if isinstance(pe_ratio, (int, float)):
244
- if 0 < pe_ratio < 15:
245
- recommendation['reasons'].append(f"Attractive PE Ratio ({pe_ratio:.2f})")
246
- elif pe_ratio > 30:
247
- recommendation['reasons'].append(f"High PE Ratio ({pe_ratio:.2f}) - potential overvaluation")
248
-
249
- # Dividend yield consideration
250
- if isinstance(dividend_yield, str) and dividend_yield != 'N/A':
251
- div_yield = float(dividend_yield.rstrip('%'))
252
- if div_yield > 3:
253
- recommendation['reasons'].append(f"Attractive dividend yield ({dividend_yield})")
254
-
255
- # Combine results
256
- recommendation_text = f"""
257
- Recommendation: {recommendation['recommendation']}
258
- Confidence: {recommendation['confidence']}
259
-
260
- Reasons:
261
- {chr(10).join('- ' + reason for reason in recommendation['reasons'])}
262
-
263
- Sentiment Breakdown:
264
- - Positive Articles: {sentiment_ratio['positive']:.2f}%
265
- - Neutral Articles: {sentiment_ratio['neutral']:.2f}%
266
- - Negative Articles: {sentiment_ratio['negative']:.2f}%
267
- """
268
 
269
- return recommendation_text
270
 
271
  def analyze_asset_sentiment(asset_input):
272
  logging.info(f"Starting sentiment analysis for asset: {asset_input}")
@@ -286,6 +335,9 @@ def analyze_asset_sentiment(asset_input):
286
  # Fetch Yahoo Finance data
287
  logging.info("Fetching Yahoo Finance data")
288
  finance_data = fetch_yfinance_data(ticker)
 
 
 
289
 
290
  # Generate stock recommendation
291
  logging.info("Generating stock recommendation")
@@ -294,71 +346,53 @@ def analyze_asset_sentiment(asset_input):
294
  logging.info("Sentiment analysis completed")
295
  return (
296
  convert_to_dataframe(analyzed_articles),
297
- finance_data,
298
- recommendation
 
299
  )
300
  except ValueError as e:
301
  logging.error(f"Error resolving ticker: {str(e)}")
302
  raise gr.Error(f"Invalid input: {str(e)}")
303
-
304
- # Update Gradio interface to include recommendation output
305
- with gr.Blocks() as iface:
306
- gr.Markdown("# Trading Asset Sentiment Analysis")
307
- gr.Markdown(
308
- "Enter the name of a trading asset, and I'll fetch recent articles, analyze their sentiment, and provide a stock recommendation!"
309
- )
310
-
311
  with gr.Row():
312
  input_asset = gr.Textbox(
313
- label="Asset Name or Ticker Symbol",
314
- lines=1,
315
- placeholder="Enter the company name or ticker symbol (e.g., Kalyan Jewellers, AAPL, TSLA)...",
316
  )
317
-
318
- with gr.Row():
319
- analyze_button = gr.Button("Analyze Sentiment", size="sm")
320
-
321
- gr.Examples(
322
- examples=[
323
- "AAPL",
324
- "TSLA",
325
- "Kalyan Jewellers",
326
- "BTC-USD"
327
- ],
328
- inputs=input_asset,
329
- )
330
-
331
- with gr.Row():
332
- with gr.Column():
333
- with gr.Blocks():
334
- gr.Markdown("## Articles and Sentiment Analysis")
335
- articles_output = gr.Dataframe(
336
- headers=["Sentiment", "Title", "Description", "Date"],
337
- datatype=["markdown", "html", "markdown", "markdown"],
338
- wrap=False,
339
- )
340
-
341
- with gr.Row():
342
- with gr.Column():
343
- with gr.Blocks():
344
- gr.Markdown("## Financial Data")
345
- finance_output = gr.JSON()
346
 
347
- with gr.Row():
348
- with gr.Column():
349
- with gr.Blocks():
350
- gr.Markdown("## Stock Recommendation")
351
- recommendation_output = gr.Textbox(
352
- label="Recommendation",
353
- lines=10,
354
- interactive=False
355
- )
356
-
357
- analyze_button.click(
358
  analyze_asset_sentiment,
359
  inputs=[input_asset],
360
- outputs=[articles_output, finance_output, recommendation_output],
361
  )
362
 
363
- logging.info("Launching Gradio interface")
364
  iface.queue().launch()
 
2
  import gradio as gr
3
  import pandas as pd
4
  import torch
5
+ import numpy as np
6
  from GoogleNews import GoogleNews
7
  from transformers import pipeline
8
  import yfinance as yf
9
  import requests
10
  from fuzzywuzzy import process
11
  import statistics
12
+ import matplotlib.pyplot as plt
13
+ from datetime import datetime, timedelta
14
 
15
  # Set up logging
16
  logging.basicConfig(
 
30
  )
31
  logging.info("Model initialized successfully")
32
 
33
+ # Technical Analysis Parameters
34
+ TA_CONFIG = {
35
+ 'rsi_window': 14,
36
+ 'macd_fast': 12,
37
+ 'macd_slow': 26,
38
+ 'macd_signal': 9,
39
+ 'bollinger_window': 20,
40
+ 'sma_windows': [20, 50, 200],
41
+ 'ema_windows': [12, 26],
42
+ 'volatility_window': 30
43
+ }
44
+
45
  EXCHANGE_SUFFIXES = {
46
  "NSE": ".NS",
47
  "BSE": ".BO",
48
  "NYSE": "",
49
  "NASDAQ": "",
 
50
  }
51
 
52
+ def calculate_technical_indicators(history):
53
+ """Calculate various technical indicators from historical price data"""
54
+ ta_results = {}
55
+
56
+ # RSI
57
+ delta = history['Close'].diff()
58
+ gain = delta.where(delta > 0, 0)
59
+ loss = -delta.where(delta < 0, 0)
60
+
61
+ avg_gain = gain.rolling(TA_CONFIG['rsi_window']).mean()
62
+ avg_loss = loss.rolling(TA_CONFIG['rsi_window']).mean()
63
+ rs = avg_gain / avg_loss
64
+ ta_results['rsi'] = 100 - (100 / (1 + rs)).iloc[-1]
65
+
66
+ # MACD
67
+ ema_fast = history['Close'].ewm(span=TA_CONFIG['macd_fast'], adjust=False).mean()
68
+ ema_slow = history['Close'].ewm(span=TA_CONFIG['macd_slow'], adjust=False).mean()
69
+ macd = ema_fast - ema_slow
70
+ signal = macd.ewm(span=TA_CONFIG['macd_signal'], adjust=False).mean()
71
+ ta_results['macd'] = macd.iloc[-1]
72
+ ta_results['macd_signal'] = signal.iloc[-1]
73
+
74
+ # Bollinger Bands
75
+ sma = history['Close'].rolling(TA_CONFIG['bollinger_window']).mean()
76
+ std = history['Close'].rolling(TA_CONFIG['bollinger_window']).std()
77
+ ta_results['bollinger_upper'] = (sma + 2 * std).iloc[-1]
78
+ ta_results['bollinger_lower'] = (sma - 2 * std).iloc[-1]
79
+
80
+ # Moving Averages
81
+ for window in TA_CONFIG['sma_windows']:
82
+ ta_results[f'sma_{window}'] = history['Close'].rolling(window).mean().iloc[-1]
83
+ for window in TA_CONFIG['ema_windows']:
84
+ ta_results[f'ema_{window}'] = history['Close'].ewm(span=window, adjust=False).mean().iloc[-1]
85
+
86
+ # Volatility
87
+ returns = history['Close'].pct_change().dropna()
88
+ ta_results['volatility_30d'] = returns.rolling(TA_CONFIG['volatility_window']).std().iloc[-1] * np.sqrt(252)
89
+
90
+ return ta_results
91
+
92
+ def generate_price_chart(history):
93
+ """Generate interactive price chart with technical indicators"""
94
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
95
+
96
+ # Price and Moving Averages
97
+ history['Close'].plot(ax=ax1, label='Price')
98
+ for window in TA_CONFIG['sma_windows']:
99
+ history['Close'].rolling(window).mean().plot(ax=ax1, label=f'SMA {window}')
100
+ ax1.set_title('Price and Moving Averages')
101
+ ax1.legend()
102
+
103
+ # RSI
104
+ delta = history['Close'].diff()
105
+ gain = delta.where(delta > 0, 0)
106
+ loss = -delta.where(delta < 0, 0)
107
+ avg_gain = gain.rolling(TA_CONFIG['rsi_window']).mean()
108
+ avg_loss = loss.rolling(TA_CONFIG['rsi_window']).mean()
109
+ rs = avg_gain / avg_loss
110
+ rsi = 100 - (100 / (1 + rs))
111
+
112
+ rsi.plot(ax=ax2, label='RSI')
113
+ ax2.axhline(70, color='red', linestyle='--')
114
+ ax2.axhline(30, color='green', linestyle='--')
115
+ ax2.set_title('Relative Strength Index (RSI)')
116
+ ax2.legend()
117
+
118
+ plt.tight_layout()
119
+ return fig
120
+
121
  def resolve_ticker_symbol(query: str, exchange: str = "NSE") -> str:
122
  """
123
  Convert company names/partial symbols to valid Yahoo Finance tickers.
 
178
  return article
179
 
180
  def fetch_yfinance_data(ticker):
181
+ """Enhanced Yahoo Finance data fetching with technical analysis"""
182
  try:
183
  logging.info(f"Fetching Yahoo Finance data for: {ticker}")
184
  stock = yf.Ticker(ticker)
185
 
186
+ # Get historical data for technical analysis
187
+ history = stock.history(period="1y", interval="1d")
 
188
 
189
+ # Calculate technical indicators
190
+ ta_data = calculate_technical_indicators(history) if not history.empty else {}
191
+
192
+ # Current price data
193
+ current_price = history['Close'].iloc[-1] if not history.empty else 0
194
+ prev_close = history['Close'].iloc[-2] if len(history) > 1 else 0
195
+ price_change = current_price - prev_close
196
+ percent_change = (price_change / prev_close) * 100 if prev_close != 0 else 0
197
+
198
+ # Generate price chart
199
+ chart = generate_price_chart(history[-120:]) # Last 120 days
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  return {
202
+ 'current_price': current_price,
203
+ 'price_change': price_change,
204
+ 'percent_change': percent_change,
205
+ 'chart': chart,
206
+ 'technical_indicators': ta_data,
207
+ 'fundamentals': stock.info
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  }
209
 
210
  except Exception as e:
211
  logging.error(f"Error fetching Yahoo Finance data: {str(e)}")
212
+ return {"error": str(e)}
213
+
214
+ def time_weighted_sentiment(articles):
215
+ """Apply time-based weighting to sentiment scores"""
216
+ now = datetime.now()
217
+ weighted_scores = []
218
+
219
+ for article in articles:
220
+ try:
221
+ article_date = datetime.strptime(article['date'], '%Y-%m-%d %H:%M:%S')
222
+ days_old = (now - article_date).days
223
+ weight = max(0, 1 - (days_old / 7)) # Linear decay over 7 days
224
+ except:
225
+ weight = 0.5 # Default weight if date parsing fails
226
+
227
+ sentiment = article['sentiment']['label']
228
+ score = 1 if sentiment == 'positive' else -1 if sentiment == 'negative' else 0
229
+ weighted_scores.append(score * weight)
230
+
231
+ return weighted_scores
232
 
233
  def _format_number(num):
234
  """Helper to format large numbers with suffixes"""
 
262
  return df[["Sentiment", "Title", "Description", "Date"]]
263
 
264
  def generate_stock_recommendation(articles, finance_data):
265
+ """Enhanced recommendation system with technical analysis"""
266
+ # Time-weighted sentiment analysis
267
+ sentiment_scores = time_weighted_sentiment(articles)
268
+ positive_score = sum(s for s in sentiment_scores if s > 0)
269
+ negative_score = abs(sum(s for s in sentiment_scores if s < 0))
270
+ total_score = positive_score - negative_score
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
+ # Technical indicators
273
+ ta = finance_data.get('technical_indicators', {})
274
+ rec = {
275
  'recommendation': 'HOLD',
276
  'confidence': 'Medium',
277
+ 'reasons': [],
278
+ 'risk_factors': []
279
  }
280
 
281
+ # Sentiment-based factors
282
+ if total_score > 3:
283
+ rec['recommendation'] = 'BUY'
284
+ rec['reasons'].append("Strong positive sentiment trend")
285
+ elif total_score < -3:
286
+ rec['recommendation'] = 'SELL'
287
+ rec['reasons'].append("Significant negative sentiment")
288
+
289
+ # Technical analysis factors
290
+ if ta.get('rsi', 50) > 70:
291
+ rec['risk_factors'].append("RSI indicates overbought condition")
292
+ elif ta.get('rsi', 50) < 30:
293
+ rec['reasons'].append("RSI suggests oversold opportunity")
294
+
295
+ if ta.get('macd', 0) > ta.get('macd_signal', 0):
296
+ rec['reasons'].append("Bullish MACD crossover")
297
+ else:
298
+ rec['risk_factors'].append("Bearish MACD trend")
299
+
300
+ # Volatility analysis
301
+ if ta.get('volatility_30d', 0) > 0.4:
302
+ rec['risk_factors'].append("High volatility detected")
303
+
304
+ # Combine factors
305
+ if len(rec['reasons']) > len(rec['risk_factors']):
306
+ rec['confidence'] = 'High'
307
+ elif len(rec['risk_factors']) > 2:
308
+ rec['recommendation'] = 'SELL' if rec['recommendation'] == 'HOLD' else rec['recommendation']
309
+ rec['confidence'] = 'Low'
310
+
311
+ # Format output
312
+ output = f"Recommendation: {rec['recommendation']} ({rec['confidence']} Confidence)\n\n"
313
+ output += "Supporting Factors:\n" + "\n".join(f"- {r}" for r in rec['reasons']) + "\n\n"
314
+ output += "Risk Factors:\n" + "\n".join(f"- {r}" for r in rec['risk_factors']) + "\n\n"
315
+ output += f"Sentiment Score: {total_score:.2f}\n"
316
+ output += f"30-Day Volatility: {ta.get('volatility_30d', 0):.2%}"
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
+ return output
319
 
320
  def analyze_asset_sentiment(asset_input):
321
  logging.info(f"Starting sentiment analysis for asset: {asset_input}")
 
335
  # Fetch Yahoo Finance data
336
  logging.info("Fetching Yahoo Finance data")
337
  finance_data = fetch_yfinance_data(ticker)
338
+
339
+ # Extract chart from finance data
340
+ price_chart = finance_data.pop('chart', None)
341
 
342
  # Generate stock recommendation
343
  logging.info("Generating stock recommendation")
 
346
  logging.info("Sentiment analysis completed")
347
  return (
348
  convert_to_dataframe(analyzed_articles),
349
+ finance_data, # Financial data without chart
350
+ recommendation,
351
+ price_chart # Chart as separate output
352
  )
353
  except ValueError as e:
354
  logging.error(f"Error resolving ticker: {str(e)}")
355
  raise gr.Error(f"Invalid input: {str(e)}")
356
+
357
+ # Update Gradio interface with new components
358
+ with gr.Blocks(theme=gr.themes.Default()) as iface:
359
+ gr.Markdown("# Advanced Trading Analytics Suite")
360
+
 
 
 
361
  with gr.Row():
362
  input_asset = gr.Textbox(
363
+ label="Asset Name/Ticker",
364
+ placeholder="Enter stock name or symbol...",
365
+ max_lines=1
366
  )
367
+ analyze_btn = gr.Button("Analyze", variant="primary")
368
+
369
+ with gr.Tabs():
370
+ with gr.TabItem("Sentiment Analysis"):
371
+ gr.Markdown("## News Sentiment Analysis")
372
+ articles_output = gr.Dataframe(
373
+ headers=["Sentiment", "Title", "Description", "Date"],
374
+ datatype=["markdown", "html", "markdown", "markdown"]
375
+ )
376
+
377
+ with gr.TabItem("Technical Analysis"):
378
+ gr.Markdown("## Technical Indicators")
379
+ with gr.Row():
380
+ price_chart = gr.Plot(label="Price Analysis")
381
+ ta_json = gr.JSON(label="Technical Indicators")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
+ with gr.TabItem("Recommendation"):
384
+ gr.Markdown("## Trading Recommendation")
385
+ recommendation_output = gr.Textbox(
386
+ lines=8,
387
+ label="Analysis Summary",
388
+ interactive=False
389
+ )
390
+
391
+ analyze_btn.click(
 
 
392
  analyze_asset_sentiment,
393
  inputs=[input_asset],
394
+ outputs=[articles_output, ta_json, recommendation_output, price_chart]
395
  )
396
 
397
+ logging.info("Launching enhanced Gradio interface")
398
  iface.queue().launch()
requirements.txt CHANGED
@@ -5,4 +5,6 @@ accelerate==0.33.0
5
  sentencepiece==0.2.0
6
  GoogleNews==1.6.14
7
  yfinance
8
- fuzzywuzzy
 
 
 
5
  sentencepiece==0.2.0
6
  GoogleNews==1.6.14
7
  yfinance
8
+ fuzzywuzzy
9
+ matplotlib
10
+ numpy