JayLacoma commited on
Commit
f2a1ca9
·
verified ·
1 Parent(s): e1b31ca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +518 -214
app.py CHANGED
@@ -1,267 +1,571 @@
1
- import gradio as gr
2
  import pandas as pd
3
- import yfinance as yf
 
 
 
 
4
  import plotly.graph_objects as go
 
 
 
 
5
  import plotly.express as px
6
- import numpy as np
7
- from datetime import datetime
8
 
9
- # Fixed high thresholds (strict = fewer, stronger signals)
10
- THRESHOLDS = {
11
- 'RSI_lower': 15, # Buy only if RSI < 15
12
- 'RSI_upper': 85, # Sell only if RSI > 85
13
- 'Stochastic_lower': 15, # Buy only if Stochastic < 15
14
- 'Stochastic_upper': 85, # Sell only if Stochastic > 85
15
- 'CMF': 0.4, # Buy if CMF < -0.4, Sell if CMF > 0.4
16
- 'BB': 5.0 # Require price 5% beyond Bollinger Band
17
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- # 8 distinct colors for tickers
20
- COLORS = px.colors.qualitative.Plotly
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- # ======================
23
- # Indicator Functions
24
- # ======================
25
 
26
- def calculate_rsi(df):
27
  delta = df['Close'].diff()
28
- gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
29
- loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
30
  rs = gain / loss
31
  rsi = 100 - (100 / (1 + rs))
32
  return rsi
33
 
34
- def calculate_bollinger_bands(df):
35
- ma = df['Close'].rolling(20).mean()
36
- std = df['Close'].rolling(20).std()
37
- return ma, ma + 2*std, ma - 2*std
 
 
 
 
 
 
 
 
38
 
39
  def calculate_stochastic_oscillator(df):
40
- ll = df['Low'].rolling(14).min()
41
- hh = df['High'].rolling(14).max()
42
- k = ((df['Close'] - ll) / (hh - ll)) * 100
43
- d = k.rolling(3).mean()
44
- return k, d
45
 
46
  def calculate_cmf(df, window=20):
47
  mfv = ((df['Close'] - df['Low']) - (df['High'] - df['Close'])) / (df['High'] - df['Low']) * df['Volume']
48
- return mfv.rolling(window).sum() / df['Volume'].rolling(window).sum()
49
-
50
- # ======================
51
- # Signal Logic (Fixed Thresholds)
52
- # ======================
53
 
54
- def generate_signals(df):
 
55
  df['RSI'] = calculate_rsi(df)
56
  df['MiddleBB'], df['UpperBB'], df['LowerBB'] = calculate_bollinger_bands(df)
57
  df['SlowK'], df['SlowD'] = calculate_stochastic_oscillator(df)
58
  df['CMF'] = calculate_cmf(df)
59
-
60
- t = THRESHOLDS
61
- df['RSI_Signal'] = np.where(df['RSI'] < t['RSI_lower'], 1,
62
- np.where(df['RSI'] > t['RSI_upper'], -1, 0))
63
 
64
- bb_buffer = t['BB'] / 100
65
- below = df['Close'] < df['LowerBB'] * (1 - bb_buffer)
66
- above = df['Close'] > df['UpperBB'] * (1 + bb_buffer)
67
- df['BB_Signal'] = np.where(below & below.shift(1), 1,
68
- np.where(above & above.shift(1), -1, 0))
 
 
 
 
 
 
69
 
70
- df['Stochastic_Signal'] = np.where((df['SlowK'] < t['Stochastic_lower']) & (df['SlowD'] < t['Stochastic_lower']), 1,
71
- np.where((df['SlowK'] > t['Stochastic_upper']) & (df['SlowD'] > t['Stochastic_upper']), -1, 0))
 
72
 
73
- df['CMF_Signal'] = np.where(df['CMF'] < -t['CMF'], 1,
74
- np.where(df['CMF'] > t['CMF'], -1, 0))
75
  return df
76
 
77
- # ======================
78
- # Ultra-Minimal Plot
79
- # ======================
80
 
81
- def plot_chart(data_dict, show_bollinger, time_range):
82
- fig = go.Figure()
83
-
84
- # Determine date range
85
- all_dates = pd.concat([df.index.to_series() for df in data_dict.values()], ignore_index=True)
86
- if all_dates.empty:
87
- return fig
88
- end = all_dates.max()
89
- start = {
90
- "1M": end - pd.DateOffset(months=1),
91
- "3M": end - pd.DateOffset(months=3),
92
- "6M": end - pd.DateOffset(months=6),
93
- "1Y": end - pd.DateOffset(years=1),
94
- "YTD": pd.to_datetime(f"{end.year}-01-01"),
95
- "All": all_dates.min()
96
- }[time_range]
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- buy_points = []
99
- sell_points = []
 
100
 
101
- for i, (ticker, df) in enumerate(data_dict.items()):
102
- df = df[df.index >= start]
103
- if df.empty:
104
- continue
105
- color = COLORS[i % len(COLORS)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- # Price line
108
- fig.add_trace(go.Scatter(
109
- x=df.index, y=df['Close'],
110
- mode='lines',
111
- line=dict(color=color, width=1.8),
112
- name=ticker,
113
- showlegend=True
114
- ))
 
 
 
 
 
 
 
 
115
 
116
- # Optional Bollinger Bands (gray, subtle)
117
- if show_bollinger:
118
- fig.add_trace(go.Scatter(
119
- x=df.index, y=df['UpperBB'],
120
- mode='lines', line=dict(color='rgba(150,150,150,0.4)', width=1, dash='dot'),
121
- showlegend=False, hoverinfo='skip'
122
- ))
123
- fig.add_trace(go.Scatter(
124
- x=df.index, y=df['LowerBB'],
125
- mode='lines', line=dict(color='rgba(150,150,150,0.4)', width=1, dash='dot'),
126
- fill='tonexty', fillcolor='rgba(150,150,150,0.05)',
127
- showlegend=False, hoverinfo='skip'
128
- ))
129
 
130
- # Collect signals
131
- for date in df.index:
132
- signals = [
133
- ('RSI', df.loc[date, 'RSI_Signal']),
134
- ('BB', df.loc[date, 'BB_Signal']),
135
- ('Stochastic', df.loc[date, 'Stochastic_Signal']),
136
- ('CMF', df.loc[date, 'CMF_Signal'])
137
- ]
138
- total = sum(sig for _, sig in signals)
139
- price = df.loc[date, 'Close']
140
- active = [name for name, sig in signals if sig == (1 if total > 0 else -1)]
141
- if total > 0:
142
- hover = f"<b>{ticker}</b><br>Buy: {', '.join(active)}<br>{date.strftime('%Y-%m-%d')}<br>${price:.2f}"
143
- buy_points.append((date, price * 0.997, hover))
144
- elif total < 0:
145
- hover = f"<b>{ticker}</b><br>Sell: {', '.join(active)}<br>{date.strftime('%Y-%m-%d')}<br>${price:.2f}"
146
- sell_points.append((date, price * 1.003, hover))
 
 
 
 
147
 
148
- # White ▲ Buy
149
- if buy_points:
150
- x, y, text = zip(*buy_points)
151
- fig.add_trace(go.Scatter(
152
- x=x, y=y,
153
- mode='markers',
154
- marker=dict(symbol='triangle-up', size=9, color='white', line=dict(color='black', width=0.8)),
155
- hovertext=text,
156
- hoverinfo='text',
157
- showlegend=False
158
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- # Black ▼ Sell
161
- if sell_points:
162
- x, y, text = zip(*sell_points)
163
- fig.add_trace(go.Scatter(
164
- x=x, y=y,
165
- mode='markers',
166
- marker=dict(symbol='triangle-down', size=9, color='black', line=dict(color='white', width=0.8)),
167
- hovertext=text,
168
- hoverinfo='text',
169
- showlegend=False
170
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
- # Ultra-minimal black theme
 
 
 
 
 
 
 
 
 
173
  fig.update_layout(
174
- xaxis=dict(showgrid=False, zeroline=False, showline=False, ticks='', showticklabels=True),
175
- yaxis=dict(showgrid=False, zeroline=False, showline=False, ticks='', showticklabels=True, tickprefix='$'),
176
- plot_bgcolor='black',
177
- paper_bgcolor='black',
178
- font=dict(color='white', size=12),
179
- showlegend=True,
180
- legend=dict(
181
- orientation='h',
182
- yanchor='bottom',
183
- y=1.02,
184
- xanchor='center',
185
- x=0.5,
186
- bgcolor='rgba(0,0,0,0.6)',
187
- font=dict(size=11)
188
- ),
189
- margin=dict(l=20, r=20, t=30, b=30),
190
- height=700,
191
- width=1100,
192
- hovermode='x unified'
193
  )
 
194
  return fig
195
 
196
- # ======================
197
- # Main Function
198
- # ======================
199
 
200
- def run_analysis(tickers_str, start_date, end_date, show_bollinger, time_range):
 
201
  try:
202
- tickers = [t.strip().upper() for t in tickers_str.split(',') if t.strip()][:8]
203
- if not tickers:
204
- raise ValueError("Enter at least one ticker symbol")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
- data = {}
207
- for t in tickers:
208
- df = yf.download(t, start=start_date, end=end_date)
209
- if not df.empty:
210
- if isinstance(df.columns, pd.MultiIndex):
211
- df.columns = df.columns.droplevel(1)
212
- data[t] = generate_signals(df)
213
 
214
- if not data:
215
- raise ValueError("No data found for the given tickers and date range")
 
 
 
216
 
217
- return plot_chart(data, show_bollinger, time_range)
 
 
 
 
 
218
 
 
 
 
 
 
 
 
 
 
 
219
  except Exception as e:
220
- fig = go.Figure()
221
- fig.add_annotation(text=f"Error: {str(e)}", x=0.5, y=0.5, showarrow=False, font=dict(color="red", size=16))
222
- fig.update_layout(plot_bgcolor='black', paper_bgcolor='black', height=700, width=1100)
223
- return fig
224
 
225
- # ======================
226
- # Gradio UI (No Threshold Sliders!)
227
- # ======================
228
-
229
- with gr.Blocks(theme=gr.themes.Default()) as demo:
230
- gr.Markdown("### 🔍 Strict Multi-Ticker Signal Viewer (Fixed High Thresholds)")
231
-
232
- with gr.Row():
233
- with gr.Column(scale=1):
234
- tickers_input = gr.Textbox(
235
- label="Tickers (comma-separated, max 8)",
236
- value="NVDA, AAPL, MSFT, TSLA"
237
- )
238
- start_input = gr.Textbox(label="Start Date", value="2022-01-01")
239
- end_input = gr.Textbox(label="End Date", value="2026-01-01")
240
-
241
- show_bb = gr.Checkbox(label="Show Bollinger Bands", value=False)
242
- time_range = gr.Radio(
243
- choices=["1M", "3M", "6M", "1Y", "YTD", "All"],
244
- value="1Y",
245
- label="Time Range"
246
- )
247
-
248
- btn = gr.Button("Analyze", variant="primary")
249
 
250
- chart = gr.Plot()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
- btn.click(
253
- run_analysis,
254
- inputs=[tickers_input, start_input, end_input, show_bb, time_range],
255
- outputs=chart
256
- )
257
 
258
- gr.Markdown("""
259
- - **▲ White** = Buy | **▼ Black** = Sell
260
- - Indicators: **RSI, Bollinger Bands, Stochastic, CMF**
261
- - Thresholds are **fixed and strict** (e.g., RSI buy < 15, sell > 85)
262
- - Toggle Bollinger Bands for context
263
- - Hover over signals to see details
264
- """)
265
 
266
  if __name__ == "__main__":
267
- demo.launch()
 
1
+ import os
2
  import pandas as pd
3
+ import requests
4
+ import numpy as np
5
+ import gradio as gr
6
+ from datetime import datetime, timedelta
7
+ from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
8
  import plotly.graph_objects as go
9
+ from plotly.subplots import make_subplots
10
+ import yfinance as yf
11
+ import warnings
12
+ from prophet import Prophet
13
  import plotly.express as px
 
 
14
 
15
+ warnings.filterwarnings('ignore')
16
+
17
+ # Configuration
18
+ class Config:
19
+ FINNHUB_API_KEY = "cuj17q1r01qm7p9n307gcuj17q1r01qm7p9n3080"
20
+ DEFAULT_DAYS = 30
21
+ DATA_DIR = "data"
22
+
23
+ @classmethod
24
+ def initialize(cls):
25
+ os.makedirs(cls.DATA_DIR, exist_ok=True)
26
+
27
+ Config.initialize()
28
+
29
+ # ============================================================================
30
+ # SENTIMENT ANALYSIS COMPONENTS
31
+ # ============================================================================
32
+
33
+ class SentimentAnalyzer:
34
+ def __init__(self):
35
+ self.analyzer = SentimentIntensityAnalyzer()
36
+
37
+ def analyze(self, text):
38
+ if not isinstance(text, str) or not text.strip():
39
+ return 0
40
+ return self.analyzer.polarity_scores(text)['compound']
41
 
42
+ class StockNewsAnalyzer:
43
+ def __init__(self, symbol):
44
+ self.symbol = symbol
45
+ self.sentiment_analyzer = SentimentAnalyzer()
46
+
47
+ def get_file_path(self, file_type):
48
+ return os.path.join(Config.DATA_DIR, f"{self.symbol}_{file_type}.csv")
49
+
50
+ def get_news(self, days=Config.DEFAULT_DAYS, force_refresh=False):
51
+ file_path = self.get_file_path("news")
52
+
53
+ if os.path.exists(file_path) and not force_refresh:
54
+ try:
55
+ return pd.read_csv(file_path, parse_dates=['datetime'])
56
+ except Exception:
57
+ pass
58
+
59
+ end_date = datetime.now()
60
+ start_date = end_date - timedelta(days=days)
61
+
62
+ url = "https://finnhub.io/api/v1/company-news"
63
+ params = {
64
+ "symbol": self.symbol,
65
+ "from": start_date.strftime('%Y-%m-%d'),
66
+ "to": end_date.strftime('%Y-%m-%d'),
67
+ "token": Config.FINNHUB_API_KEY,
68
+ }
69
+
70
+ try:
71
+ response = requests.get(url, params=params, timeout=10)
72
+ data = response.json()
73
+
74
+ if not data or not isinstance(data, list):
75
+ return pd.DataFrame()
76
+
77
+ df = pd.DataFrame(data)
78
+ if 'datetime' in df.columns:
79
+ df['datetime'] = pd.to_datetime(df['datetime'], unit='s')
80
+ df.to_csv(file_path, index=False)
81
+ return df
82
+ return pd.DataFrame()
83
+ except Exception as e:
84
+ print(f"Error fetching news: {e}")
85
+ return pd.DataFrame()
86
+
87
+ def get_sentiment_score(self, days=30):
88
+ news_df = self.get_news(days)
89
+ if news_df.empty:
90
+ return 0, 0
91
+
92
+ if 'headline' in news_df.columns:
93
+ news_df['sentiment_score'] = news_df['headline'].apply(self.sentiment_analyzer.analyze)
94
+ avg_sentiment = news_df['sentiment_score'].mean()
95
+ article_count = len(news_df)
96
+ return avg_sentiment, article_count
97
+ return 0, 0
98
 
99
+ # ============================================================================
100
+ # TECHNICAL ANALYSIS COMPONENTS
101
+ # ============================================================================
102
 
103
+ def calculate_rsi(df, window=14):
104
  delta = df['Close'].diff()
105
+ gain = (delta.where(delta > 0, 0)).rolling(window=window).mean()
106
+ loss = (-delta.where(delta < 0, 0)).rolling(window=window).mean()
107
  rs = gain / loss
108
  rsi = 100 - (100 / (1 + rs))
109
  return rsi
110
 
111
+ def calculate_macd(df):
112
+ short_ema = df['Close'].ewm(span=12, adjust=False).mean()
113
+ long_ema = df['Close'].ewm(span=26, adjust=False).mean()
114
+ macd = short_ema - long_ema
115
+ signal = macd.ewm(span=9, adjust=False).mean()
116
+ return macd, signal
117
+
118
+ def calculate_bollinger_bands(df, window=20):
119
+ middle_bb = df['Close'].rolling(window=window).mean()
120
+ upper_bb = middle_bb + 2 * df['Close'].rolling(window=window).std()
121
+ lower_bb = middle_bb - 2 * df['Close'].rolling(window=window).std()
122
+ return middle_bb, upper_bb, lower_bb
123
 
124
  def calculate_stochastic_oscillator(df):
125
+ lowest_low = df['Low'].rolling(window=14).min()
126
+ highest_high = df['High'].rolling(window=14).max()
127
+ slowk = ((df['Close'] - lowest_low) / (highest_high - lowest_low)) * 100
128
+ slowd = slowk.rolling(window=3).mean()
129
+ return slowk, slowd
130
 
131
  def calculate_cmf(df, window=20):
132
  mfv = ((df['Close'] - df['Low']) - (df['High'] - df['Close'])) / (df['High'] - df['Low']) * df['Volume']
133
+ cmf = mfv.rolling(window=window).sum() / df['Volume'].rolling(window=window).sum()
134
+ return cmf
 
 
 
135
 
136
+ def calculate_technical_signals(df):
137
+ """Calculate all technical indicators and generate signals"""
138
  df['RSI'] = calculate_rsi(df)
139
  df['MiddleBB'], df['UpperBB'], df['LowerBB'] = calculate_bollinger_bands(df)
140
  df['SlowK'], df['SlowD'] = calculate_stochastic_oscillator(df)
141
  df['CMF'] = calculate_cmf(df)
 
 
 
 
142
 
143
+ macd, signal = calculate_macd(df)
144
+
145
+ # Generate signals (1 = buy, -1 = sell, 0 = neutral)
146
+ df['RSI_Signal'] = np.where(df['RSI'] < 30, 1, np.where(df['RSI'] > 70, -1, 0))
147
+ df['MACD_Signal'] = np.where((macd > signal) & (macd.shift(1) <= signal.shift(1)), 1,
148
+ np.where((macd < signal) & (macd.shift(1) >= signal.shift(1)), -1, 0))
149
+ df['BB_Signal'] = np.where(df['Close'] < df['LowerBB'], 1,
150
+ np.where(df['Close'] > df['UpperBB'], -1, 0))
151
+ df['Stochastic_Signal'] = np.where((df['SlowK'] < 20) & (df['SlowD'] < 20), 1,
152
+ np.where((df['SlowK'] > 80) & (df['SlowD'] > 80), -1, 0))
153
+ df['CMF_Signal'] = np.where(df['CMF'] > 0.2, 1, np.where(df['CMF'] < -0.2, -1, 0))
154
 
155
+ # Combined technical signal
156
+ technical_signals = ['RSI_Signal', 'MACD_Signal', 'BB_Signal', 'Stochastic_Signal', 'CMF_Signal']
157
+ df['Technical_Score'] = df[technical_signals].sum(axis=1)
158
 
 
 
159
  return df
160
 
161
+ # ============================================================================
162
+ # FORECASTING COMPONENTS
163
+ # ============================================================================
164
 
165
+ def prophet_forecast_simple(df, days=30):
166
+ """Simple Prophet forecast returning just the trend direction"""
167
+ try:
168
+ # Prepare data for Prophet
169
+ prophet_df = df.reset_index()[['Date', 'Close']].rename(columns={'Date': 'ds', 'Close': 'y'})
170
+ prophet_df['ds'] = pd.to_datetime(prophet_df['ds'])
171
+
172
+ # Fit model
173
+ model = Prophet(daily_seasonality=False, yearly_seasonality=False, weekly_seasonality=False)
174
+ model.fit(prophet_df)
175
+
176
+ # Make future dataframe
177
+ future = model.make_future_dataframe(periods=days)
178
+ forecast = model.predict(future)
179
+
180
+ # Get current price and forecasted price
181
+ current_price = prophet_df['y'].iloc[-1]
182
+ future_price = forecast['yhat'].iloc[-1]
183
+
184
+ # Calculate percentage change
185
+ pct_change = ((future_price - current_price) / current_price) * 100
186
+
187
+ return pct_change, future_price
188
+
189
+ except Exception as e:
190
+ print(f"Forecast error: {e}")
191
+ return 0, 0
192
 
193
+ # ============================================================================
194
+ # INTEGRATED DECISION ENGINE
195
+ # ============================================================================
196
 
197
+ class TradingDecisionEngine:
198
+ def __init__(self, symbol):
199
+ self.symbol = symbol.upper()
200
+ self.news_analyzer = StockNewsAnalyzer(symbol)
201
+
202
+ def fetch_stock_data(self, start_date, end_date):
203
+ """Fetch stock data"""
204
+ try:
205
+ stock_data = yf.download(self.symbol, start=start_date, end=end_date)
206
+ if isinstance(stock_data.columns, pd.MultiIndex):
207
+ stock_data.columns = stock_data.columns.droplevel(1)
208
+ return stock_data
209
+ except Exception as e:
210
+ print(f"Error fetching stock data: {e}")
211
+ return pd.DataFrame()
212
+
213
+ def analyze_comprehensive(self, days_back=90):
214
+ """Comprehensive analysis combining all factors"""
215
+ try:
216
+ # Fetch stock data
217
+ end_date = datetime.now()
218
+ start_date = end_date - timedelta(days=days_back)
219
+
220
+ df = self.fetch_stock_data(start_date, end_date)
221
+ if df.empty:
222
+ return None
223
+
224
+ # Technical analysis
225
+ df = calculate_technical_signals(df)
226
+
227
+ # Sentiment analysis
228
+ sentiment_score, article_count = self.news_analyzer.get_sentiment_score(days=30)
229
+
230
+ # Forecast
231
+ forecast_change, forecast_price = prophet_forecast_simple(df, days=30)
232
+
233
+ # Current metrics
234
+ current_price = df['Close'].iloc[-1]
235
+ current_technical = df['Technical_Score'].iloc[-1]
236
+ current_rsi = df['RSI'].iloc[-1]
237
+ current_macd, current_signal = calculate_macd(df)
238
+
239
+ # Decision scoring
240
+ scores = {
241
+ 'sentiment': self._score_sentiment(sentiment_score),
242
+ 'technical': self._score_technical(current_technical),
243
+ 'forecast': self._score_forecast(forecast_change),
244
+ 'rsi': self._score_rsi(current_rsi),
245
+ 'momentum': self._score_momentum(df)
246
+ }
247
+
248
+ # Calculate final decision
249
+ total_score = sum(scores.values())
250
+ decision = self._make_decision(total_score)
251
+
252
+ return {
253
+ 'symbol': self.symbol,
254
+ 'current_price': current_price,
255
+ 'decision': decision,
256
+ 'total_score': total_score,
257
+ 'scores': scores,
258
+ 'sentiment_score': sentiment_score,
259
+ 'article_count': article_count,
260
+ 'technical_score': current_technical,
261
+ 'forecast_change': forecast_change,
262
+ 'forecast_price': forecast_price,
263
+ 'rsi': current_rsi,
264
+ 'dataframe': df
265
+ }
266
+
267
+ except Exception as e:
268
+ print(f"Analysis error: {e}")
269
+ return None
270
+
271
+ def _score_sentiment(self, sentiment):
272
+ """Score sentiment from -2 to +2"""
273
+ if sentiment > 0.3: return 2
274
+ elif sentiment > 0.1: return 1
275
+ elif sentiment > -0.1: return 0
276
+ elif sentiment > -0.3: return -1
277
+ else: return -2
278
+
279
+ def _score_technical(self, technical_score):
280
+ """Score technical indicators from -2 to +2"""
281
+ if technical_score >= 3: return 2
282
+ elif technical_score >= 1: return 1
283
+ elif technical_score <= -3: return -2
284
+ elif technical_score <= -1: return -1
285
+ else: return 0
286
+
287
+ def _score_forecast(self, forecast_change):
288
+ """Score forecast from -2 to +2"""
289
+ if forecast_change > 10: return 2
290
+ elif forecast_change > 5: return 1
291
+ elif forecast_change < -10: return -2
292
+ elif forecast_change < -5: return -1
293
+ else: return 0
294
+
295
+ def _score_rsi(self, rsi):
296
+ """Score RSI from -2 to +2"""
297
+ if rsi < 20: return 2 # Very oversold - strong buy
298
+ elif rsi < 30: return 1 # Oversold - buy
299
+ elif rsi > 80: return -2 # Very overbought - strong sell
300
+ elif rsi > 70: return -1 # Overbought - sell
301
+ else: return 0
302
+
303
+ def _score_momentum(self, df):
304
+ """Score price momentum"""
305
+ if len(df) < 10:
306
+ return 0
307
+
308
+ current = df['Close'].iloc[-1]
309
+ week_ago = df['Close'].iloc[-5] if len(df) >= 5 else current
310
+
311
+ change = ((current - week_ago) / week_ago) * 100
312
 
313
+ if change > 5: return 1
314
+ elif change < -5: return -1
315
+ else: return 0
316
+
317
+ def _make_decision(self, total_score):
318
+ """Make final trading decision"""
319
+ if total_score >= 5:
320
+ return "STRONG BUY"
321
+ elif total_score >= 2:
322
+ return "BUY"
323
+ elif total_score <= -5:
324
+ return "STRONG SELL"
325
+ elif total_score <= -2:
326
+ return "SELL"
327
+ else:
328
+ return "HOLD"
329
 
330
+ # ============================================================================
331
+ # VISUALIZATION FUNCTIONS
332
+ # ============================================================================
 
 
 
 
 
 
 
 
 
 
333
 
334
+ def create_decision_dashboard(analysis_result):
335
+ """Create comprehensive dashboard"""
336
+ if not analysis_result:
337
+ return None, None, None
338
+
339
+ # Extract data
340
+ symbol = analysis_result['symbol']
341
+ decision = analysis_result['decision']
342
+ scores = analysis_result['scores']
343
+ df = analysis_result['dataframe']
344
+
345
+ # 1. Decision Summary Chart
346
+ fig_summary = create_summary_chart(analysis_result)
347
+
348
+ # 2. Technical Analysis Chart
349
+ fig_technical = create_technical_chart(df, symbol)
350
+
351
+ # 3. Score Breakdown Chart
352
+ fig_scores = create_scores_chart(scores, analysis_result['total_score'])
353
+
354
+ return fig_summary, fig_technical, fig_scores
355
 
356
+ def create_summary_chart(analysis):
357
+ """Create summary dashboard"""
358
+ fig = go.Figure()
359
+
360
+ # Decision color mapping
361
+ decision_colors = {
362
+ 'STRONG BUY': '#00FF00',
363
+ 'BUY': '#90EE90',
364
+ 'HOLD': '#FFD700',
365
+ 'SELL': '#FFA500',
366
+ 'STRONG SELL': '#FF0000'
367
+ }
368
+
369
+ decision = analysis['decision']
370
+ color = decision_colors.get(decision, '#FFD700')
371
+
372
+ # Create gauge chart for decision strength
373
+ fig.add_trace(go.Indicator(
374
+ mode="gauge+number+delta",
375
+ value=analysis['total_score'],
376
+ domain={'x': [0, 1], 'y': [0, 1]},
377
+ title={'text': f"{analysis['symbol']} - {decision}"},
378
+ delta={'reference': 0},
379
+ gauge={
380
+ 'axis': {'range': [None, 10]},
381
+ 'bar': {'color': color},
382
+ 'steps': [
383
+ {'range': [-10, -2], 'color': "lightgray"},
384
+ {'range': [-2, 2], 'color': "gray"},
385
+ {'range': [2, 10], 'color': "lightgreen"}
386
+ ],
387
+ 'threshold': {
388
+ 'line': {'color': "red", 'width': 4},
389
+ 'thickness': 0.75,
390
+ 'value': 90
391
+ }
392
+ }
393
+ ))
394
+
395
+ fig.update_layout(
396
+ paper_bgcolor='#111111',
397
+ plot_bgcolor='#111111',
398
+ font={'color': "white"},
399
+ height=400
400
+ )
401
+
402
+ return fig
403
 
404
+ def create_technical_chart(df, symbol):
405
+ """Create technical analysis chart"""
406
+ fig = make_subplots(rows=3, cols=1,
407
+ subplot_titles=('Price & Bollinger Bands', 'RSI', 'MACD'),
408
+ vertical_spacing=0.05,
409
+ row_heights=[0.6, 0.2, 0.2])
410
+
411
+ # Price and Bollinger Bands
412
+ fig.add_trace(go.Scatter(x=df.index, y=df['Close'], name='Close Price',
413
+ line=dict(color='#00FFFF', width=2)), row=1, col=1)
414
+ fig.add_trace(go.Scatter(x=df.index, y=df['UpperBB'], name='Upper BB',
415
+ line=dict(color='red', dash='dash')), row=1, col=1)
416
+ fig.add_trace(go.Scatter(x=df.index, y=df['LowerBB'], name='Lower BB',
417
+ line=dict(color='red', dash='dash')), row=1, col=1)
418
+
419
+ # RSI
420
+ fig.add_trace(go.Scatter(x=df.index, y=df['RSI'], name='RSI',
421
+ line=dict(color='#FF6B6B')), row=2, col=1)
422
+ fig.add_hline(y=70, line_dash="dash", line_color="red", row=2, col=1)
423
+ fig.add_hline(y=30, line_dash="dash", line_color="green", row=2, col=1)
424
+
425
+ # MACD
426
+ macd, signal = calculate_macd(df)
427
+ fig.add_trace(go.Scatter(x=df.index, y=macd, name='MACD',
428
+ line=dict(color='#4ECDC4')), row=3, col=1)
429
+ fig.add_trace(go.Scatter(x=df.index, y=signal, name='Signal',
430
+ line=dict(color='#FFE66D')), row=3, col=1)
431
+
432
+ fig.update_layout(
433
+ title=f'{symbol} Technical Analysis',
434
+ plot_bgcolor='#111111',
435
+ paper_bgcolor='#111111',
436
+ font=dict(color='white'),
437
+ height=800,
438
+ showlegend=False
439
+ )
440
+
441
+ return fig
442
 
443
+ def create_scores_chart(scores, total_score):
444
+ """Create score breakdown chart"""
445
+ categories = list(scores.keys())
446
+ values = list(scores.values())
447
+ colors = ['#00FF00' if v > 0 else '#FF0000' if v < 0 else '#FFD700' for v in values]
448
+
449
+ fig = go.Figure(data=[
450
+ go.Bar(x=categories, y=values, marker_color=colors, text=values, textposition='auto')
451
+ ])
452
+
453
  fig.update_layout(
454
+ title=f'Score Breakdown (Total: {total_score})',
455
+ plot_bgcolor='#111111',
456
+ paper_bgcolor='#111111',
457
+ font=dict(color='white'),
458
+ yaxis=dict(range=[-3, 3]),
459
+ height=400
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  )
461
+
462
  return fig
463
 
464
+ # ============================================================================
465
+ # GRADIO INTERFACE
466
+ # ============================================================================
467
 
468
+ def analyze_stock_comprehensive(symbol, days_back):
469
+ """Main analysis function for Gradio"""
470
  try:
471
+ if not symbol:
472
+ return "Please enter a valid stock symbol.", None, None, None, "No data available."
473
+
474
+ # Create decision engine
475
+ engine = TradingDecisionEngine(symbol)
476
+
477
+ # Run comprehensive analysis
478
+ analysis = engine.analyze_comprehensive(days_back)
479
+
480
+ if not analysis:
481
+ return "Unable to analyze this stock. Please check the symbol and try again.", None, None, None, "No data available."
482
+
483
+ # Create visualizations
484
+ fig_summary, fig_technical, fig_scores = create_decision_dashboard(analysis)
485
+
486
+ # Create summary text
487
+ summary = f"""
488
+ # {analysis['symbol']} Trading Analysis
489
 
490
+ ## 🎯 RECOMMENDATION: {analysis['decision']}
491
+ **Current Price:** ${analysis['current_price']:.2f}
492
+ **Overall Score:** {analysis['total_score']}/10
 
 
 
 
493
 
494
+ ### 📊 Analysis Breakdown:
495
+ - **Sentiment Score:** {analysis['sentiment_score']:.3f} ({analysis['article_count']} articles)
496
+ - **Technical Score:** {analysis['technical_score']}
497
+ - **Forecast:** {analysis['forecast_change']:.1f}% (Target: ${analysis['forecast_price']:.2f})
498
+ - **RSI:** {analysis['rsi']:.1f}
499
 
500
+ ### 🔍 Individual Scores:
501
+ - **Sentiment:** {analysis['scores']['sentiment']}/2
502
+ - **Technical:** {analysis['scores']['technical']}/2
503
+ - **Forecast:** {analysis['scores']['forecast']}/2
504
+ - **RSI:** {analysis['scores']['rsi']}/2
505
+ - **Momentum:** {analysis['scores']['momentum']}/2
506
 
507
+ ### 📈 Decision Logic:
508
+ - **Strong Buy (≥5):** Multiple positive signals align
509
+ - **Buy (≥2):** More positive than negative signals
510
+ - **Hold (-1 to 1):** Mixed or neutral signals
511
+ - **Sell (≤-2):** More negative than positive signals
512
+ - **Strong Sell (≤-5):** Multiple negative signals align
513
+ """
514
+
515
+ return summary, fig_summary, fig_technical, fig_scores, "Analysis completed successfully!"
516
+
517
  except Exception as e:
518
+ return f"Error during analysis: {str(e)}", None, None, None, "Analysis failed."
 
 
 
519
 
520
+ # Build Gradio interface
521
+ def build_interface():
522
+ """Create the integrated Gradio interface"""
523
+ with gr.Blocks(title="Integrated Trading Decision App", theme=gr.themes.Soft()) as app:
524
+ gr.Markdown("# 📈 Integrated Stock Trading Decision System")
525
+ gr.Markdown("**Combines sentiment analysis, technical indicators, and AI forecasting for comprehensive trading decisions**")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
 
527
+ with gr.Row():
528
+ with gr.Column(scale=1):
529
+ symbol_input = gr.Textbox(
530
+ label="Stock Symbol",
531
+ value="AAPL",
532
+ placeholder="e.g., AAPL, MSFT, GOOGL, TSLA"
533
+ )
534
+ days_input = gr.Slider(
535
+ label="Analysis Period (Days)",
536
+ minimum=30,
537
+ maximum=365,
538
+ value=90,
539
+ step=1
540
+ )
541
+ analyze_button = gr.Button("🔍 Analyze Stock", variant="primary", size="lg")
542
+
543
+ # Status
544
+ status = gr.Textbox(label="Status", interactive=False)
545
+
546
+ # Summary
547
+ summary_text = gr.Markdown()
548
+
549
+ # Charts
550
+ with gr.Row():
551
+ decision_chart = gr.Plot(label="📊 Decision Summary")
552
+ scores_chart = gr.Plot(label="📈 Score Breakdown")
553
+
554
+ technical_chart = gr.Plot(label="📉 Technical Analysis")
555
+
556
+ # Set up event handler
557
+ analyze_button.click(
558
+ fn=analyze_stock_comprehensive,
559
+ inputs=[symbol_input, days_input],
560
+ outputs=[summary_text, decision_chart, technical_chart, scores_chart, status]
561
+ )
562
 
563
+ return app
 
 
 
 
564
 
565
+ # Main function
566
+ def main():
567
+ app = build_interface()
568
+ app.launch(share=True, debug=True)
 
 
 
569
 
570
  if __name__ == "__main__":
571
+ main()