Premchan369 commited on
Commit
691c8ee
Β·
verified Β·
1 Parent(s): 4e436ea

Full AlphaForge app with all 4 tabs - stock analysis, portfolio, AI chat, about

Browse files
Files changed (1) hide show
  1. app.py +503 -16
app.py CHANGED
@@ -1,20 +1,507 @@
 
 
 
1
  import os
2
  import gradio as gr
 
 
 
 
 
 
3
 
4
  K2_KEY = os.environ.get("K2_API_KEY", "")
5
- K2_OK = bool(K2_KEY) and len(K2_KEY) > 5
6
-
7
- def greet(name):
8
- k2_status = "βœ… Connected" if K2_OK else "❌ Not configured (set K2_API_KEY secret)"
9
- return f"Hello {name}! K2 Think V2 status: {k2_status}"
10
-
11
- with gr.Blocks(title="AlphaForge x K2 Think V2") as demo:
12
- gr.Markdown("# πŸ”₯ AlphaForge x K2 Think V2")
13
- gr.Markdown("### Elite Quantitative Trading Platform")
14
-
15
- name = gr.Textbox(label="Your name", value="Trader")
16
- output = gr.Textbox(label="Greeting")
17
- button = gr.Button("Greet")
18
- button.click(fn=greet, inputs=name, outputs=output)
19
-
20
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AlphaForge x K2 Think V2 β€” Elite Quant Trading Demo
2
+ Full-featured interactive app for the Build with K2 Think V2 Challenge.
3
+ """
4
  import os
5
  import gradio as gr
6
+ import requests
7
+ import yfinance as yf
8
+ import pandas as pd
9
+ import numpy as np
10
+ import plotly.graph_objects as go
11
+ from plotly.subplots import make_subplots
12
 
13
  K2_KEY = os.environ.get("K2_API_KEY", "")
14
+ K2_URL = "https://api.k2think.ai/v1/chat/completions"
15
+ K2_MODEL = "MBZUAI-IFM/K2-Think-v2"
16
+
17
+ # ═══════════════════════════════════════════════════════════════
18
+ # K2 THINK V2 CLIENT
19
+ # ═══════════════════════════════════════════════════════════════
20
+ class K2Client:
21
+ def __init__(self):
22
+ self.key = K2_KEY
23
+ self.ok = bool(self.key) and len(self.key) > 5
24
+
25
+ def ask(self, prompt, temp=0.7):
26
+ if not self.ok:
27
+ return """⚠️ **K2 Think V2 API Not Configured**
28
+
29
+ The AI features require an API key. To enable them:
30
+
31
+ 1. Go to **Space Settings** β†’ **Repository secrets**
32
+ 2. Click **New secret**
33
+ 3. Name: `K2_API_KEY`
34
+ 4. Value: `IFM-4SpQ0qEg0Wlsw04O`
35
+ 5. Click **Save**, then **Factory Rebuild**
36
+
37
+ βœ… All technical analysis, charts, and portfolio tools work **without** the API key!"""
38
+ try:
39
+ r = requests.post(
40
+ K2_URL,
41
+ headers={
42
+ "Authorization": f"Bearer {self.key}",
43
+ "Content-Type": "application/json"
44
+ },
45
+ json={
46
+ "model": K2_MODEL,
47
+ "messages": [{"role": "user", "content": prompt}],
48
+ "temperature": temp,
49
+ "max_tokens": 4096,
50
+ "stream": False
51
+ },
52
+ timeout=90
53
+ )
54
+ r.raise_for_status()
55
+ j = r.json()
56
+ return j.get("choices", [{}])[0].get("message", {}).get("content", "No response")
57
+ except requests.exceptions.Timeout:
58
+ return "⏱️ K2 API timed out. Please try again."
59
+ except requests.exceptions.HTTPError as e:
60
+ if e.response.status_code == 401:
61
+ return "πŸ” API key invalid. Please check K2_API_KEY secret."
62
+ elif e.response.status_code == 429:
63
+ return "🚦 Rate limit hit. Please wait a moment."
64
+ return f"πŸ”΄ HTTP Error {e.response.status_code}: {str(e)[:200]}"
65
+ except Exception as e:
66
+ return f"πŸ”΄ Error: {str(e)[:200]}. All other features work fine!"
67
+
68
+ # ═══════════════════════════════════════════════════════════════
69
+ # MARKET DATA
70
+ # ═══════════════════════════════════════════════════════════════
71
+ def fetch_data(ticker, period="6mo"):
72
+ try:
73
+ df = yf.Ticker(ticker.upper().strip()).history(period=period)
74
+ if df.empty:
75
+ return None, f"No data for '{ticker}'. Try: AAPL, TSLA, SPY, BTC-USD, ETH-USD"
76
+ return df, None
77
+ except Exception as e:
78
+ return None, f"Error fetching '{ticker}': {str(e)[:200]}"
79
+
80
+ # ═══════════════════════════════════════════════════════════════
81
+ # TECHNICAL INDICATORS
82
+ # ═══════════════════════════════════════════════════════════════
83
+ def calc_indicators(df):
84
+ df = df.copy()
85
+ df['Ret'] = df['Close'].pct_change()
86
+ df['SMA20'] = df['Close'].rolling(20).mean()
87
+ df['SMA50'] = df['Close'].rolling(50).mean()
88
+ df['EMA12'] = df['Close'].ewm(span=12, adjust=False).mean()
89
+ df['EMA26'] = df['Close'].ewm(span=26, adjust=False).mean()
90
+ df['MACD'] = df['EMA12'] - df['EMA26']
91
+ df['MACDS'] = df['MACD'].ewm(span=9, adjust=False).mean()
92
+ df['MACDH'] = df['MACD'] - df['MACDS']
93
+ d = df['Close'].diff()
94
+ g = d.where(d > 0, 0).rolling(14).mean()
95
+ l = (-d.where(d < 0, 0)).rolling(14).mean()
96
+ df['RSI'] = 100 - (100 / (1 + g / (l + 1e-10)))
97
+ m = df['Close'].rolling(20).mean()
98
+ s = df['Close'].rolling(20).std()
99
+ df['BBU'] = m + 2*s
100
+ df['BBL'] = m - 2*s
101
+ df['BBP'] = (df['Close'] - df['BBL']) / (df['BBU'] - df['BBL'] + 1e-10)
102
+ tp = (df['High'] + df['Low'] + df['Close']) / 3
103
+ df['VWAP'] = (tp * df['Volume']).cumsum() / (df['Volume'].cumsum() + 1e-10)
104
+ tr = pd.concat([df['High']-df['Low'], np.abs(df['High']-df['Close'].shift()), np.abs(df['Low']-df['Close'].shift())], axis=1).max(axis=1)
105
+ df['ATR'] = tr.rolling(14).mean()
106
+ lo = df['Low'].rolling(14).min()
107
+ hi = df['High'].rolling(14).max()
108
+ df['SK'] = 100 * (df['Close'] - lo) / (hi - lo + 1e-10)
109
+ df['VM'] = df['Volume'].rolling(20).mean()
110
+ df['VR'] = df['Volume'] / (df['VM'] + 1e-10)
111
+ return df
112
+
113
+ def calc_risk(df):
114
+ r = df['Ret'].dropna()
115
+ if len(r) < 30:
116
+ return {}
117
+ ar = r.mean() * 252
118
+ av = r.std() * np.sqrt(252)
119
+ sh = ar / (av + 1e-10)
120
+ dn = r[r < 0]
121
+ sd = dn.std() * np.sqrt(252) if len(dn) > 0 else 1e-10
122
+ so = ar / (sd + 1e-10)
123
+ c = (1 + r).cumprod()
124
+ rm = c.expanding().max()
125
+ md = ((c - rm) / rm).min()
126
+ v95 = np.percentile(r, 5)
127
+ cv95 = r[r <= v95].mean() if len(r[r <= v95]) > 0 else v95
128
+ ca = ar / (abs(md) + 1e-10)
129
+ return {'ar': ar, 'av': av, 'sh': sh, 'so': so, 'md': md,
130
+ 'v95': v95, 'cv95': cv95, 'ca': ca,
131
+ 'sk': r.skew(), 'ku': r.kurtosis(),
132
+ 'wr': (r > 0).mean(), 'pf': abs(r[r > 0].sum() / (r[r < 0].sum() + 1e-10))}
133
+
134
+ def calc_signals(df):
135
+ l = df.iloc[-1]
136
+ p = df.iloc[-2] if len(df) > 1 else l
137
+ s = {'trend': 'neutral', 'mom': 'neutral', 'score': 50}
138
+ if l['Close'] > l['SMA20'] > l['SMA50']:
139
+ s['trend'] = 'bullish'
140
+ elif l['Close'] < l['SMA20'] < l['SMA50']:
141
+ s['trend'] = 'bearish'
142
+ if l['RSI'] < 30:
143
+ s['mom'] = 'oversold'
144
+ elif l['RSI'] > 70:
145
+ s['mom'] = 'overbought'
146
+ elif l['MACD'] > l['MACDS'] and p['MACD'] <= p['MACDS']:
147
+ s['mom'] = 'bullish crossover'
148
+ elif l['MACD'] < l['MACDS'] and p['MACD'] >= p['MACDS']:
149
+ s['mom'] = 'bearish crossover'
150
+ sc = 50
151
+ if s['trend'] == 'bullish': sc += 15
152
+ if s['trend'] == 'bearish': sc -= 15
153
+ if 'oversold' in s['mom']: sc += 10
154
+ if 'overbought' in s['mom']: sc -= 10
155
+ if 'bullish crossover' in s['mom']: sc += 10
156
+ if 'bearish crossover' in s['mom']: sc -= 10
157
+ if l['Close'] > l['VWAP']: sc += 5
158
+ if l['Close'] < l['VWAP']: sc -= 5
159
+ s['score'] = max(0, min(100, sc))
160
+ s['dir'] = '🟒 BULLISH' if sc > 60 else 'πŸ”΄ BEARISH' if sc < 40 else 'βšͺ NEUTRAL'
161
+ return s
162
+
163
+ # ═══════════════════════════════════════════════════════════════
164
+ # CHARTS
165
+ # ═══════════════════════════════════════════════════════════════
166
+ def make_candle(df, ticker):
167
+ fig = make_subplots(
168
+ rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.03,
169
+ row_heights=[0.65, 0.2, 0.15],
170
+ subplot_titles=(f'{ticker} Price', 'Volume', 'RSI')
171
+ )
172
+ colors = ['green' if df['Close'].iloc[i] >= df['Open'].iloc[i] else 'red' for i in range(len(df))]
173
+ fig.add_trace(go.Candlestick(x=df.index, open=df['Open'], high=df['High'], low=df['Low'], close=df['Close'], name='Price'), row=1, col=1)
174
+ fig.add_trace(go.Scatter(x=df.index, y=df['SMA20'], line=dict(color='orange', width=1), name='SMA20', legendgroup='1'), row=1, col=1)
175
+ fig.add_trace(go.Scatter(x=df.index, y=df['SMA50'], line=dict(color='blue', width=1), name='SMA50', legendgroup='1'), row=1, col=1)
176
+ fig.add_trace(go.Scatter(x=df.index, y=df['BBU'], line=dict(color='gray', dash='dash', width=1), name='BB+', opacity=0.4), row=1, col=1)
177
+ fig.add_trace(go.Scatter(x=df.index, y=df['BBL'], line=dict(color='gray', dash='dash', width=1), name='BB-', opacity=0.4), row=1, col=1)
178
+ fig.add_trace(go.Bar(x=df.index, y=df['Volume'], marker_color=colors, name='Volume', opacity=0.6), row=2, col=1)
179
+ fig.add_trace(go.Scatter(x=df.index, y=df['RSI'], line=dict(color='purple', width=1.5), name='RSI', legendgroup='2'), row=3, col=1)
180
+ fig.add_hline(y=70, line_dash="dash", line_color="red", row=3, col=1)
181
+ fig.add_hline(y=30, line_dash="dash", line_color="green", row=3, col=1)
182
+ fig.update_layout(height=750, template='plotly_white', showlegend=False, xaxis_rangeslider_visible=False)
183
+ fig.update_yaxes(title_text="Price", row=1, col=1)
184
+ fig.update_yaxes(title_text="Volume", row=2, col=1)
185
+ fig.update_yaxes(title_text="RSI", range=[0, 100], row=3, col=1)
186
+ return fig
187
+
188
+ def make_macd(df, ticker):
189
+ fig = go.Figure()
190
+ fig.add_trace(go.Scatter(x=df.index, y=df['MACD'], line=dict(color='blue', width=1.5), name='MACD'))
191
+ fig.add_trace(go.Scatter(x=df.index, y=df['MACDS'], line=dict(color='orange', width=1.5), name='Signal'))
192
+ fig.add_trace(go.Bar(x=df.index, y=df['MACDH'], marker_color=['green' if v >= 0 else 'red' for v in df['MACDH']], name='Histogram', opacity=0.5))
193
+ fig.update_layout(title=f'{ticker} MACD', height=350, template='plotly_white', showlegend=False)
194
+ return fig
195
+
196
+ def make_dist(r, ticker):
197
+ fig = go.Figure()
198
+ fig.add_trace(go.Histogram(x=r, nbinsx=50, marker_color='steelblue', opacity=0.7, name='Returns'))
199
+ fig.update_layout(title=f'{ticker} Return Distribution', xaxis_title='Daily Return', yaxis_title='Frequency', height=350, template='plotly_white', bargap=0.1)
200
+ return fig
201
+
202
+ # ═══════════════════════════════════════════════════════════════
203
+ # ANALYZE STOCK
204
+ # ═══════════════════════════════════════════════════════════════
205
+ def analyze_stock(ticker, period="6mo"):
206
+ ticker = ticker.strip().upper()
207
+ if not ticker:
208
+ return None, None, None, "## ❌ Please enter a ticker symbol (e.g., AAPL, TSLA, SPY, BTC-USD)."
209
+ df, err = fetch_data(ticker, period)
210
+ if err:
211
+ return None, None, None, f"## ❌ {err}"
212
+ df = calc_indicators(df)
213
+ sg = calc_signals(df)
214
+ rk = calc_risk(df)
215
+ if not rk:
216
+ return None, None, None, "## ❌ Insufficient data. Try a longer period (e.g., 6mo or 1y)."
217
+ l = df.iloc[-1]
218
+ p = df.iloc[-2] if len(df) > 1 else l
219
+ ch = ((l['Close']/p['Close']-1)*100) if p['Close'] > 0 else 0
220
+
221
+ md = f"""# {ticker} β€” {sg['dir']} (Score: {sg['score']}/100)
222
+
223
+ **Price:** ${l['Close']:.2f} | **Change:** {ch:+.2f}% | **Period:** {period}
224
+
225
+ | Indicator | Value | Signal |
226
+ |-----------|-------|--------|
227
+ | RSI | {l['RSI']:.1f} | {'🟒 Oversold' if l['RSI']<30 else 'πŸ”΄ Overbought' if l['RSI']>70 else 'βšͺ Neutral'} |
228
+ | MACD | {l['MACD']:.3f} | {'🟒 Bullish' if l['MACD']>l['MACDS'] else 'πŸ”΄ Bearish'} |
229
+ | Bollinger | {l['BBP']:.1%} | {'πŸ”΄ Upper' if l['BBP']>0.8 else '🟒 Lower' if l['BBP']<0.2 else 'βšͺ Mid'} |
230
+ | VWAP | {'🟒 Above' if l['Close']>l['VWAP'] else 'πŸ”΄ Below'} | {'🟒 Bullish' if l['Close']>l['VWAP'] else 'πŸ”΄ Bearish'} |
231
+ | Volume | {l['VR']:.1f}x avg | {'πŸ”₯ Heavy' if l['VR']>1.5 else 'βšͺ Normal'} |
232
+ | Trend | {sg['trend'].upper()} | β€” |
233
+ | Momentum | {sg['mom']} | β€” |
234
+
235
+ ## Risk Metrics
236
+ | Metric | Value |
237
+ |--------|-------|
238
+ | Ann. Return | {rk['ar']*100:.1f}% |
239
+ | Ann. Volatility | {rk['av']*100:.1f}% |
240
+ | Sharpe | {rk['sh']:.2f} |
241
+ | Sortino | {rk['so']:.2f} |
242
+ | Max Drawdown | {rk['md']*100:.1f}% |
243
+ | VaR (95%) | {rk['v95']*100:.2f}% |
244
+ | CVaR (95%) | {rk['cv95']*100:.2f}% |
245
+ | Calmar | {rk['ca']:.2f} |
246
+ | Win Rate | {rk['wr']*100:.1f}% |
247
+ | Profit Factor | {rk['pf']:.2f} |
248
+ | Skewness | {rk['sk']:.2f} |
249
+ | Kurtosis | {rk['ku']:.2f} |
250
+ """
251
+ return make_candle(df, ticker), make_macd(df, ticker), make_dist(df['Ret'].dropna(), ticker), md
252
+
253
+ def ai_analyze(ticker, period="6mo"):
254
+ ticker = ticker.strip().upper()
255
+ if not ticker:
256
+ return "Enter a ticker symbol."
257
+ df, err = fetch_data(ticker, period)
258
+ if err:
259
+ return f"Error: {err}"
260
+ df = calc_indicators(df)
261
+ sg = calc_signals(df)
262
+ rk = calc_risk(df)
263
+ l = df.iloc[-1]
264
+ ds = f"Ticker: {ticker}\nPrice: ${l['Close']:.2f}\nSMA20: ${l['SMA20']:.2f}\nSMA50: ${l['SMA50']:.2f}\n52W High: ${df['High'].max():.2f}\n52W Low: ${df['Low'].min():.2f}\nATR: ${l['ATR']:.2f}"
265
+ ts = f"RSI: {l['RSI']:.1f}\nMACD: {l['MACD']:.3f} vs Signal: {l['MACDS']:.3f}\nBBP: {l['BBP']:.1%}\nSK: {l['SK']:.1f}\nVWAP: ${l['VWAP']:.2f}\nScore: {sg['score']}/100 | Dir: {sg['dir']}\nSharpe: {rk.get('sh',0):.2f}\nVol: {rk.get('av',0)*100:.1f}%\nMaxDD: {rk.get('md',0)*100:.1f}%\nVaR95: {rk.get('v95',0)*100:.2f}%"
266
+ prompt = f"""You are an elite quantitative analyst at Jane Street/Two Sigma. Analyze with deep reasoning.
267
+
268
+ TICKER: {ticker}
269
+
270
+ DATA: {ds}
271
+
272
+ TECHNICAL: {ts}
273
+
274
+ Provide:
275
+ 1. Executive Summary (3 bullets)
276
+ 2. Technical Analysis β€” interpret RSI, MACD, Bollinger, VWAP
277
+ 3. Risk Assessment β€” volatility regime, VaR, tail risks
278
+ 4. Alpha Signal β€” Bullish/Neutral/Bearish with confidence %
279
+ 5. Trade Idea β€” entry price, stop-loss, target price
280
+ 6. Catalyst Watch β€” events that could move the stock
281
+
282
+ Think step-by-step."""
283
+ return K2Client().ask(prompt, temp=0.3)
284
+
285
+ # ═══════════════════════════════════════════════════════════════
286
+ # PORTFOLIO
287
+ # ═══════════════════════════════════════════════════════════════
288
+ def opt_portfolio(tickers, period="1y"):
289
+ ts = [t.strip().upper() for t in tickers.split(',') if t.strip()]
290
+ if len(ts) < 2:
291
+ return None, None, "## ❌ Enter at least 2 tickers separated by commas (e.g., AAPL, MSFT, GOOGL)."
292
+ data = {}
293
+ errs = []
294
+ for t in ts:
295
+ df, err = fetch_data(t, period)
296
+ if err:
297
+ errs.append(err)
298
+ elif df is not None and len(df) > 30:
299
+ data[t] = df['Close']
300
+ if len(data) < 2:
301
+ return None, None, f"## ❌ Could not fetch enough data. Errors: {'; '.join(errs[:3])}"
302
+ prices = pd.DataFrame(data).dropna()
303
+ returns = prices.pct_change().dropna()
304
+ if len(returns) < 30:
305
+ return None, None, "## ❌ Insufficient aligned data. Try different tickers or a longer period."
306
+ mu = returns.mean() * 252
307
+ sigma = returns.cov() * 252
308
+ n = len(mu)
309
+ best_sh = -999
310
+ best_w = np.ones(n) / n
311
+ np.random.seed(42)
312
+ for _ in range(3000):
313
+ w = np.random.dirichlet(np.ones(n) * 0.5)
314
+ w = np.clip(w, 0, 0.5)
315
+ w = w / w.sum()
316
+ pr = np.dot(w, mu)
317
+ pv = np.sqrt(np.dot(w.T, np.dot(sigma, w)))
318
+ sh = pr / (pv + 1e-10)
319
+ if sh > best_sh:
320
+ best_sh = sh
321
+ best_w = w
322
+ pr = np.dot(best_w, mu)
323
+ pv = np.sqrt(np.dot(best_w.T, np.dot(sigma, best_w)))
324
+ eq_w = np.ones(n) / n
325
+ eq_r = np.dot(eq_w, mu)
326
+ eq_v = np.sqrt(np.dot(eq_w.T, np.dot(sigma, eq_w)))
327
+
328
+ # Efficient frontier
329
+ ws = np.random.dirichlet(np.ones(n) * 0.5, 2000)
330
+ ws = np.clip(ws, 0, 0.5)
331
+ ws = ws / ws.sum(axis=1, keepdims=True)
332
+ prets = np.dot(ws, mu)
333
+ pvols = np.array([np.sqrt(np.dot(w.T, np.dot(sigma, w))) for w in ws])
334
+ psh = prets / (pvols + 1e-10)
335
+ fig = go.Figure()
336
+ fig.add_trace(go.Scatter(x=pvols, y=prets, mode='markers',
337
+ marker=dict(size=4, color=psh, colorscale='Viridis', showscale=True, colorbar=dict(title='Sharpe')),
338
+ name='Portfolios'))
339
+ fig.add_trace(go.Scatter(x=[pv], y=[pr], mode='markers+text',
340
+ marker=dict(size=16, color='red', symbol='star'), text=['Optimal'], textposition='top center'))
341
+ fig.add_trace(go.Scatter(x=[eq_v], y=[eq_r], mode='markers+text',
342
+ marker=dict(size=12, color='orange', symbol='diamond'), text=['Equal'], textposition='bottom center'))
343
+ fig.update_layout(title='Efficient Frontier', xaxis_title='Volatility', yaxis_title='Return',
344
+ template='plotly_white', height=500, showlegend=False)
345
+
346
+ wdf = pd.DataFrame({'Ticker': list(data.keys()), 'Optimal (%)': np.round(best_w * 100, 2), 'Equal (%)': np.round(eq_w * 100, 2)})
347
+
348
+ md = f"""# Portfolio Results
349
+
350
+ **Tickers:** {', '.join(list(data.keys()))}
351
+
352
+ | | Optimal | Equal Weight |
353
+ |-|---------|-------------|
354
+ | Expected Return | {pr*100:.1f}% | {eq_r*100:.1f}% |
355
+ | Volatility | {pv*100:.1f}% | {eq_v*100:.1f}% |
356
+ | Sharpe | {best_sh:.2f} | {eq_r/(eq_v+1e-10):.2f} |
357
+
358
+ **Improvements:** Sharpe {((best_sh/(eq_r/(eq_v+1e-10))-1)*100):+.1f}%
359
+
360
+ {wdf.to_markdown(index=False)}
361
+ """
362
+ return fig, wdf, md
363
+
364
+ def ai_portfolio(tickers, period):
365
+ fig, wdf, md = opt_portfolio(tickers, period)
366
+ if fig is None:
367
+ return f"Error: {md}"
368
+ pd_str = f"Tickers: {', '.join(wdf['Ticker'].tolist())}\nWeights: {', '.join([f'{t}: {w:.1f}%' for t, w in zip(wdf['Ticker'], wdf['Optimal (%)'])])}"
369
+ prompt = f"""You are a portfolio manager at a quant hedge fund ($500M AUM).
370
+
371
+ PORTFOLIO:
372
+ {pd_str}
373
+
374
+ Provide:
375
+ 1. Portfolio Health Score (0-100)
376
+ 2. Concentration risk analysis
377
+ 3. Correlation risks
378
+ 4. Rebalancing recommendations with exact %
379
+ 5. Hedging strategy for tail risk
380
+ 6. Expected return & Sharpe estimate
381
+
382
+ Use quantitative reasoning."""
383
+ return K2Client().ask(prompt, temp=0.3)
384
+
385
+ # ═══════════════════════════════════════════════════════════════
386
+ # GRADIO UI
387
+ # ═══════════════════════════════════════════════════════════════
388
+ with gr.Blocks(
389
+ title="AlphaForge x K2 Think V2 β€” Elite Quant Trading",
390
+ theme=gr.themes.Soft(),
391
+ css="""
392
+ .title { text-align: center; font-size: 2.2em; font-weight: bold; color: #1a73e8; }
393
+ .subtitle { text-align: center; font-size: 1em; color: #5f6368; }
394
+ .badge { background: linear-gradient(90deg, #667eea, #764ba2); color: white; padding: 6px 12px; border-radius: 16px; font-weight: 600; font-size: 0.85em; }
395
+ .info-box { background: #e8f4fd; border-left: 4px solid #1a73e8; padding: 10px; margin: 6px 0; border-radius: 4px; }
396
+ .warn-box { background: #fff3e0; border-left: 4px solid #f9ab00; padding: 10px; margin: 6px 0; border-radius: 4px; }
397
+ """
398
+ ) as demo:
399
+ gr.HTML("""
400
+ <div style="text-align:center;">
401
+ <h1 class="title">πŸ”₯ AlphaForge x K2 Think V2</h1>
402
+ <p class="subtitle">Elite Quantitative Trading Platform β€” Powered by MBZUAI's State-of-the-Art Reasoning Model</p>
403
+ <p>
404
+ <span class="badge">πŸ€– K2 Think V2</span>
405
+ <span style="margin-left:6px;" class="badge">πŸ“Š Real-Time Data</span>
406
+ <span style="margin-left:6px;" class="badge">🎯 AI Alpha</span>
407
+ </p>
408
+ </div>
409
+ """)
410
+
411
+ # ── TAB 1: Single Stock ──
412
+ with gr.Tab("πŸ“ˆ Single Stock Analysis"):
413
+ with gr.Row():
414
+ with gr.Column(scale=1):
415
+ t_in = gr.Textbox(label="Stock Ticker", placeholder="e.g., AAPL, TSLA, SPY, BTC-USD", value="AAPL")
416
+ p_in = gr.Dropdown(label="Time Period", choices=["1mo","3mo","6mo","1y","2y","5y"], value="6mo")
417
+ gr.HTML("""<div class="info-box">πŸ’‘ <b>Tip:</b> Try AAPL, TSLA, NVDA, SPY, QQQ, BTC-USD, ETH-USD</div>""")
418
+ a_btn = gr.Button("πŸ” Analyze Stock", variant="primary", size="lg")
419
+ ai_btn = gr.Button("πŸ€– AI Deep Analysis (K2 Think V2)", variant="secondary", size="lg")
420
+ gr.HTML("""<div class="warn-box">⚠️ <b>K2 API:</b> AI features need API key. See About tab for setup.</div>""")
421
+ with gr.Column(scale=2):
422
+ out_md = gr.Markdown()
423
+ with gr.Row():
424
+ out_candle = gr.Plot(label="πŸ“Š Price Chart")
425
+ out_macd = gr.Plot(label="πŸ“ˆ MACD")
426
+ with gr.Row():
427
+ out_dist = gr.Plot(label="πŸ“‰ Return Distribution")
428
+ out_ai = gr.Textbox(label="πŸ€– K2 Think V2 AI Analysis", lines=20, max_lines=35, show_copy_button=True)
429
+ a_btn.click(fn=analyze_stock, inputs=[t_in, p_in], outputs=[out_candle, out_macd, out_dist, out_md])
430
+ ai_btn.click(fn=ai_analyze, inputs=[t_in, p_in], outputs=[out_ai])
431
+
432
+ # ── TAB 2: Portfolio ──
433
+ with gr.Tab("πŸ’Ό Portfolio Optimizer"):
434
+ with gr.Row():
435
+ with gr.Column(scale=1):
436
+ pt_in = gr.Textbox(label="Tickers (comma-separated)", placeholder="e.g., AAPL, MSFT, GOOGL, AMZN, NVDA", value="AAPL, MSFT, GOOGL, AMZN, NVDA")
437
+ pp_in = gr.Dropdown(label="Lookback Period", choices=["6mo","1y","2y","3y"], value="1y")
438
+ gr.HTML("""<div class="info-box">πŸ’‘ <b>Tip:</b> Enter 2-10 tickers. Optimizer maximizes Sharpe ratio.</div>""")
439
+ o_btn = gr.Button("🎯 Optimize Portfolio", variant="primary", size="lg")
440
+ ai_p_btn = gr.Button("πŸ€– AI Portfolio Advice (K2 Think V2)", variant="secondary", size="lg")
441
+ with gr.Column(scale=2):
442
+ p_md = gr.Markdown()
443
+ with gr.Row():
444
+ out_frontier = gr.Plot(label="πŸ“Š Efficient Frontier")
445
+ out_weights = gr.DataFrame(label="βš–οΈ Optimal Weights", interactive=False)
446
+ with gr.Row():
447
+ out_ai_p = gr.Textbox(label="πŸ€– AI Portfolio Advice", lines=20, max_lines=30, show_copy_button=True)
448
+ o_btn.click(fn=opt_portfolio, inputs=[pt_in, pp_in], outputs=[out_frontier, out_weights, p_md])
449
+ ai_p_btn.click(fn=ai_portfolio, inputs=[pt_in, pp_in], outputs=[out_ai_p])
450
+
451
+ # ── TAB 3: AI Chat ──
452
+ with gr.Tab("πŸ’¬ K2 Think V2 Chat"):
453
+ gr.Markdown("## πŸ’¬ Direct Chat with K2 Think V2")
454
+ gr.Markdown("Ask any financial question, strategy idea, or market analysis. The AI provides detailed, reasoned responses.")
455
+ c_in = gr.Textbox(label="Your Question", placeholder="e.g., 'Analyze the bull case for AI stocks in 2025' or 'Explain pairs trading'", lines=4)
456
+ c_temp = gr.Slider(label="Temperature (creativity)", minimum=0, maximum=1, value=0.5, step=0.1,
457
+ info="Lower = more factual, Higher = more creative")
458
+ c_btn = gr.Button("πŸš€ Ask K2 Think V2", variant="primary", size="lg")
459
+ c_out = gr.Textbox(label="πŸ€– K2 Think V2 Response", lines=25, max_lines=40, show_copy_button=True)
460
+ c_btn.click(fn=lambda q, t: K2Client().ask(q, temp=t), inputs=[c_in, c_temp], outputs=[c_out])
461
+
462
+ # ── TAB 4: About ──
463
+ with gr.Tab("ℹ️ About & Setup"):
464
+ gr.Markdown("""
465
+ ## ℹ️ About AlphaForge x K2 Think V2
466
+
467
+ Built for the **Build with K2 Think V2 Challenge** by MBZUAI.
468
+
469
+ ### πŸš€ Features
470
+ - **πŸ“ˆ Single Stock Analysis**: Candlestick + Bollinger + SMA + VWAP, MACD, RSI, 12 risk metrics, composite alpha signal
471
+ - **πŸ€– AI Deep Analysis**: K2 Think V2 chain-of-thought reasoning β€” executive summary, risk assessment, trade ideas with entry/stop/target
472
+ - **πŸ’Ό Portfolio Optimizer**: Mean-variance optimization, efficient frontier (2000 portfolios), Sharpe maximization, optimal vs equal-weight
473
+ - **πŸ€– AI Portfolio Advice**: Portfolio health score, concentration risk, rebalancing %, hedging strategies
474
+ - **πŸ’¬ Direct AI Chat**: Ask any financial question β€” strategy explanations, market analysis, quant interview prep
475
+
476
+ ### πŸ”§ Setup Instructions
477
+
478
+ <div style="background:#fff3e0;border-left:4px solid #f9ab00;padding:10px;margin:6px 0;border-radius:4px;">
479
+ ⚠️ <b>K2 Think V2 API Key Required for AI features</b><br><br>
480
+ 1. Go to <b>Space Settings</b> β†’ <b>Repository secrets</b><br>
481
+ 2. Click <b>New secret</b><br>
482
+ 3. Name: <code>K2_API_KEY</code><br>
483
+ 4. Value: <code>IFM-4SpQ0qEg0Wlsw04O</code><br>
484
+ 5. Click <b>Save</b>, then <b>Factory Rebuild</b>
485
+ </div>
486
+
487
+ <div style="background:#e8f4fd;border-left:4px solid #1a73e8;padding:10px;margin:6px 0;border-radius:4px;">
488
+ βœ… <b>Even without API key:</b> All technical analysis, charts, risk metrics, and portfolio optimization work perfectly!
489
+ </div>
490
+
491
+ ### πŸ— Architecture
492
+ ```
493
+ yfinance (live data) β†’ AlphaForge (quant signals) β†’ K2 Think V2 (AI reasoning)
494
+ ↓
495
+ Plotly Charts + Risk Metrics + Portfolio Optimization
496
+ ```
497
+
498
+ ### πŸ”— Links
499
+ - [Full AlphaForge Platform](https://huggingface.co/Premchan369/alphaforge-quant-system) (25 quant modules)
500
+ - [Build with K2 Think V2](https://build.k2think.ai/)
501
+ - [MBZUAI](https://mbzuai.ac.ae/)
502
+
503
+ ---
504
+ *Built by Premchan | Build with K2 Think V2 Challenge | MBZUAI*
505
+ """)
506
+
507
+ demo.launch(server_name="0.0.0.0", server_port=7860)