Premchan369 commited on
Commit
d009b3f
Β·
verified Β·
1 Parent(s): b507572

Restore original app.py (1050 lines) + share=True

Browse files
Files changed (1) hide show
  1. app.py +1050 -1
app.py CHANGED
@@ -1 +1,1050 @@
1
- Downloaded file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AlphaForge x K2 Think V2 β€” Elite Quant Trading Demo
2
+
3
+ Built for the Build with K2 Think V2 Challenge (MBZUAI)
4
+
5
+ Features:
6
+ - K2 Think V2 API integration for AI-powered financial reasoning
7
+ - Real-time stock data via yfinance
8
+ - Technical analysis (RSI, MACD, Bollinger, VWAP)
9
+ - Portfolio optimization & risk metrics
10
+ - Alpha signal generation
11
+ - AI-powered market analysis with chain-of-thought reasoning
12
+
13
+ API Key: IFM-4SpQ0qEg0Wlsw04O
14
+ """
15
+ import os
16
+ import json
17
+ import time
18
+ import gradio as gr
19
+ import requests
20
+ import yfinance as yf
21
+ import pandas as pd
22
+ import numpy as np
23
+ from datetime import datetime, timedelta
24
+ import plotly.graph_objects as go
25
+ from plotly.subplots import make_subplots
26
+ import warnings
27
+ warnings.filterwarnings('ignore')
28
+
29
+
30
+ # ──────────────────────────────────────────────────────────────
31
+ # K2 Think V2 API Configuration
32
+ # ──────────────────────────────────────────────────────────────
33
+ K2_API_KEY = os.environ.get("K2_API_KEY", "IFM-4SpQ0qEg0Wlsw04O")
34
+ K2_BASE_URL = "https://api.k2think.ai/v1/chat/completions"
35
+ K2_MODEL = "MBZUAI-IFM/K2-Think-v2"
36
+
37
+
38
+ # ──────────────────────────────────────────────────────────────
39
+ # K2 Think V2 API Client
40
+ # ──────────────────────────────────────────────────────────────
41
+ class K2ThinkClient:
42
+ """Client for K2 Think V2 API β€” state-of-the-art reasoning model"""
43
+
44
+ def __init__(self, api_key: str = K2_API_KEY, base_url: str = K2_BASE_URL):
45
+ self.api_key = api_key
46
+ self.base_url = base_url
47
+ self.headers = {
48
+ "accept": "application/json",
49
+ "Authorization": f"Bearer {api_key}",
50
+ "Content-Type": "application/json"
51
+ }
52
+
53
+ def chat(self, messages: list, temperature: float = 0.7,
54
+ max_tokens: int = 4096, stream: bool = False) -> str:
55
+ """Send chat completion request to K2 Think V2"""
56
+ payload = {
57
+ "model": K2_MODEL,
58
+ "messages": messages,
59
+ "temperature": temperature,
60
+ "max_tokens": max_tokens,
61
+ "stream": stream
62
+ }
63
+
64
+ try:
65
+ response = requests.post(
66
+ self.base_url,
67
+ headers=self.headers,
68
+ json=payload,
69
+ timeout=120
70
+ )
71
+ response.raise_for_status()
72
+ result = response.json()
73
+
74
+ if 'choices' in result and len(result['choices']) > 0:
75
+ return result['choices'][0]['message']['content']
76
+ return f"Error: Unexpected response format: {result}"
77
+
78
+ except requests.exceptions.Timeout:
79
+ return "Error: Request timed out. K2 Think V2 API may be experiencing high load."
80
+ except requests.exceptions.RequestException as e:
81
+ return f"Error: API request failed - {str(e)}"
82
+
83
+ def analyze_market(self, ticker: str, data_summary: str,
84
+ technical_summary: str) -> str:
85
+ """Use K2 Think V2 to analyze market conditions"""
86
+ prompt = f"""You are an elite quantitative analyst at a top hedge fund (Jane Street / Two Sigma level).
87
+ Analyze the following financial data with deep reasoning and chain-of-thought analysis.
88
+
89
+ TICKER: {ticker}
90
+
91
+ MARKET DATA SUMMARY:
92
+ {data_summary}
93
+
94
+ TECHNICAL INDICATORS:
95
+ {technical_summary}
96
+
97
+ Please provide:
98
+ 1. **Executive Summary** β€” Key findings in 3 bullet points
99
+ 2. **Technical Analysis** β€” Interpret RSI, MACD, Bollinger Bands signals
100
+ 3. **Risk Assessment** β€” Volatility regime, tail risks, VaR estimation
101
+ 4. **Alpha Signal** β€” Directional bias (Bullish/Neutral/Bearish) with confidence %
102
+ 5. **Actionable Recommendation** β€” Specific trade idea with entry, stop-loss, target
103
+ 6. **Catalyst Watch** β€” What news/events could move this ticker next week
104
+
105
+ Think step-by-step and show your reasoning clearly."""
106
+
107
+ messages = [{"role": "user", "content": prompt}]
108
+ return self.chat(messages, temperature=0.3, max_tokens=4096)
109
+
110
+ def portfolio_advice(self, portfolio_data: str, risk_metrics: str) -> str:
111
+ """AI-powered portfolio optimization advice"""
112
+ prompt = f"""You are a portfolio manager at a quant hedge fund managing $500M AUM.
113
+ Provide institutional-grade portfolio analysis.
114
+
115
+ PORTFOLIO HOLDINGS:
116
+ {portfolio_data}
117
+
118
+ RISK METRICS:
119
+ {risk_metrics}
120
+
121
+ Please provide:
122
+ 1. **Portfolio Health Score** (0-100) and grade
123
+ 2. **Concentration Risk** β€” Are we over-exposed to any sector/style?
124
+ 3. **Correlation Analysis** β€” Hidden risks from correlated positions
125
+ 4. **Rebalancing Recommendation** β€” Specific weight adjustments with %
126
+ 5. **Hedging Strategy** β€” Options/ETFs to reduce tail risk
127
+ 6. **Expected Return & Sharpe** β€” Forward-looking estimate
128
+
129
+ Use quantitative reasoning. Reference specific numbers from the data."""
130
+
131
+ messages = [{"role": "user", "content": prompt}]
132
+ return self.chat(messages, temperature=0.3, max_tokens=4096)
133
+
134
+ def explain_strategy(self, strategy_name: str, metrics: str) -> str:
135
+ """Explain trading strategy performance with AI reasoning"""
136
+ prompt = f"""Explain this trading strategy's performance like you're teaching a quant interview candidate.
137
+
138
+ STRATEGY: {strategy_name}
139
+
140
+ PERFORMANCE METRICS:
141
+ {metrics}
142
+
143
+ Provide:
144
+ 1. **Strategy Intuition** β€” What economic/behavioral mechanism does it exploit?
145
+ 2. **Performance Attribution** β€” Break down P&L sources (alpha, beta, luck)
146
+ 3. **Risk-Adjusted Quality** β€” Sharpe, Sortino, max drawdown analysis
147
+ 4. **Benchmark Comparison** β€” How does it compare to buy-and-hold?
148
+ 5. **Edge Sustainability** β€” Will this alpha persist or decay?
149
+ 6. **Improvement Ideas** β€” 3 specific enhancements with expected impact
150
+
151
+ Be insightful and educational."""
152
+
153
+ messages = [{"role": "user", "content": prompt}]
154
+ return self.chat(messages, temperature=0.5, max_tokens=4096)
155
+
156
+
157
+ # ──────────────────────────────────────────────────────────────
158
+ # Market Data & Technical Analysis
159
+ # ──────────────────────────────────────────────────────────────
160
+ def fetch_stock_data(ticker: str, period: str = "6mo", interval: str = "1d"):
161
+ """Fetch stock data with error handling"""
162
+ try:
163
+ stock = yf.Ticker(ticker.upper().strip())
164
+ df = stock.history(period=period, interval=interval)
165
+
166
+ if df.empty:
167
+ return None, f"No data found for {ticker}. Please check the ticker symbol."
168
+
169
+ info = stock.info
170
+ return df, info
171
+ except Exception as e:
172
+ return None, f"Error fetching data: {str(e)}"
173
+
174
+
175
+ def compute_technical_indicators(df: pd.DataFrame) -> pd.DataFrame:
176
+ """Compute technical indicators"""
177
+ df = df.copy()
178
+
179
+ # Returns
180
+ df['Returns'] = df['Close'].pct_change()
181
+
182
+ # Simple Moving Averages
183
+ df['SMA_20'] = df['Close'].rolling(20).mean()
184
+ df['SMA_50'] = df['Close'].rolling(50).mean()
185
+ df['SMA_200'] = df['Close'].rolling(200).mean()
186
+
187
+ # EMA
188
+ df['EMA_12'] = df['Close'].ewm(span=12, adjust=False).mean()
189
+ df['EMA_26'] = df['Close'].ewm(span=26, adjust=False).mean()
190
+
191
+ # MACD
192
+ df['MACD'] = df['EMA_12'] - df['EMA_26']
193
+ df['MACD_Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
194
+ df['MACD_Histogram'] = df['MACD'] - df['MACD_Signal']
195
+
196
+ # RSI
197
+ delta = df['Close'].diff()
198
+ gain = (delta.where(delta > 0, 0)).rolling(14).mean()
199
+ loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
200
+ rs = gain / (loss + 1e-10)
201
+ df['RSI'] = 100 - (100 / (1 + rs))
202
+
203
+ # Bollinger Bands
204
+ df['BB_Middle'] = df['Close'].rolling(20).mean()
205
+ bb_std = df['Close'].rolling(20).std()
206
+ df['BB_Upper'] = df['BB_Middle'] + 2 * bb_std
207
+ df['BB_Lower'] = df['BB_Middle'] - 2 * bb_std
208
+ df['BB_Width'] = (df['BB_Upper'] - df['BB_Lower']) / df['BB_Middle']
209
+ df['BB_Position'] = (df['Close'] - df['BB_Lower']) / (df['BB_Upper'] - df['BB_Lower'] + 1e-10)
210
+
211
+ # VWAP
212
+ typical_price = (df['High'] + df['Low'] + df['Close']) / 3
213
+ df['VWAP'] = (typical_price * df['Volume']).cumsum() / (df['Volume'].cumsum() + 1e-10)
214
+
215
+ # ATR (Average True Range)
216
+ high_low = df['High'] - df['Low']
217
+ high_close = np.abs(df['High'] - df['Close'].shift())
218
+ low_close = np.abs(df['Low'] - df['Close'].shift())
219
+ tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
220
+ df['ATR'] = tr.rolling(14).mean()
221
+
222
+ # Stochastic Oscillator
223
+ low_14 = df['Low'].rolling(14).min()
224
+ high_14 = df['High'].rolling(14).max()
225
+ df['Stoch_K'] = 100 * (df['Close'] - low_14) / (high_14 - low_14 + 1e-10)
226
+ df['Stoch_D'] = df['Stoch_K'].rolling(3).mean()
227
+
228
+ # Volume indicators
229
+ df['Volume_MA'] = df['Volume'].rolling(20).mean()
230
+ df['Volume_Ratio'] = df['Volume'] / (df['Volume_MA'] + 1e-10)
231
+
232
+ return df
233
+
234
+
235
+ def generate_signals(df: pd.DataFrame) -> dict:
236
+ """Generate alpha signals from technical indicators"""
237
+ latest = df.iloc[-1]
238
+ prev = df.iloc[-2] if len(df) > 1 else latest
239
+
240
+ signals = {
241
+ 'trend': 'neutral',
242
+ 'momentum': 'neutral',
243
+ 'volatility': 'normal',
244
+ 'volume': 'normal',
245
+ 'composite_score': 50,
246
+ 'confidence': 50
247
+ }
248
+
249
+ # Trend signal
250
+ if latest['Close'] > latest['SMA_20'] > latest['SMA_50']:
251
+ signals['trend'] = 'bullish'
252
+ elif latest['Close'] < latest['SMA_20'] < latest['SMA_50']:
253
+ signals['trend'] = 'bearish'
254
+
255
+ # Momentum signal
256
+ if latest['RSI'] < 30:
257
+ signals['momentum'] = 'oversold (bullish bounce potential)'
258
+ elif latest['RSI'] > 70:
259
+ signals['momentum'] = 'overbought (bearish pullback risk)'
260
+ elif latest['MACD'] > latest['MACD_Signal'] and prev['MACD'] <= prev['MACD_Signal']:
261
+ signals['momentum'] = 'MACD bullish crossover'
262
+ elif latest['MACD'] < latest['MACD_Signal'] and prev['MACD'] >= prev['MACD_Signal']:
263
+ signals['momentum'] = 'MACD bearish crossover'
264
+
265
+ # Volatility signal
266
+ bb_width = latest['BB_Width']
267
+ if bb_width > df['BB_Width'].quantile(0.9):
268
+ signals['volatility'] = 'expanding (breakout likely)'
269
+ elif bb_width < df['BB_Width'].quantile(0.1):
270
+ signals['volatility'] = 'contracting (squeeze setup)'
271
+
272
+ # Volume signal
273
+ if latest['Volume_Ratio'] > 2.0:
274
+ signals['volume'] = 'heavy (institutional interest)'
275
+
276
+ # Composite score (0-100, >60 bullish, <40 bearish)
277
+ score = 50
278
+ if signals['trend'] == 'bullish': score += 15
279
+ if signals['trend'] == 'bearish': score -= 15
280
+ if 'oversold' in signals['momentum']: score += 10
281
+ if 'overbought' in signals['momentum']: score -= 10
282
+ if 'bullish crossover' in signals['momentum']: score += 10
283
+ if 'bearish crossover' in signals['momentum']: score -= 10
284
+ if latest['Close'] > latest['VWAP']: score += 5
285
+ if latest['Close'] < latest['VWAP']: score -= 5
286
+
287
+ signals['composite_score'] = max(0, min(100, score))
288
+
289
+ # Confidence based on indicator agreement
290
+ bullish_count = sum([
291
+ signals['trend'] == 'bullish',
292
+ 'oversold' in signals['momentum'] or 'bullish crossover' in signals['momentum'],
293
+ latest['Close'] > latest['VWAP']
294
+ ])
295
+ bearish_count = sum([
296
+ signals['trend'] == 'bearish',
297
+ 'overbought' in signals['momentum'] or 'bearish crossover' in signals['momentum'],
298
+ latest['Close'] < latest['VWAP']
299
+ ])
300
+
301
+ signals['confidence'] = max(bullish_count, bearish_count) * 33 + 1
302
+ signals['direction'] = 'BULLISH' if score > 60 else 'BEARISH' if score < 40 else 'NEUTRAL'
303
+
304
+ return signals
305
+
306
+
307
+ def compute_risk_metrics(df: pd.DataFrame) -> dict:
308
+ """Compute portfolio risk metrics"""
309
+ returns = df['Returns'].dropna()
310
+
311
+ if len(returns) < 30:
312
+ return {}
313
+
314
+ # Basic stats
315
+ annual_return = returns.mean() * 252
316
+ annual_vol = returns.std() * np.sqrt(252)
317
+ sharpe = annual_return / (annual_vol + 1e-10)
318
+
319
+ # Sortino (downside deviation)
320
+ downside = returns[returns < 0]
321
+ downside_dev = downside.std() * np.sqrt(252) if len(downside) > 0 else 1e-10
322
+ sortino = annual_return / (downside_dev + 1e-10)
323
+
324
+ # Max drawdown
325
+ cumulative = (1 + returns).cumprod()
326
+ running_max = cumulative.expanding().max()
327
+ drawdown = (cumulative - running_max) / running_max
328
+ max_dd = drawdown.min()
329
+
330
+ # VaR / CVaR
331
+ var_95 = np.percentile(returns, 5)
332
+ var_99 = np.percentile(returns, 1)
333
+ cvar_95 = returns[returns <= var_95].mean() if len(returns[returns <= var_95]) > 0 else var_95
334
+
335
+ # Calmar
336
+ calmar = annual_return / (abs(max_dd) + 1e-10)
337
+
338
+ # Skewness and kurtosis
339
+ skew = returns.skew()
340
+ kurt = returns.kurtosis()
341
+
342
+ return {
343
+ 'annual_return': annual_return,
344
+ 'annual_volatility': annual_vol,
345
+ 'sharpe_ratio': sharpe,
346
+ 'sortino_ratio': sortino,
347
+ 'max_drawdown': max_dd,
348
+ 'var_95_daily': var_95,
349
+ 'var_99_daily': var_99,
350
+ 'cvar_95_daily': cvar_95,
351
+ 'calmar_ratio': calmar,
352
+ 'skewness': skew,
353
+ 'excess_kurtosis': kurt,
354
+ 'win_rate': (returns > 0).mean(),
355
+ 'avg_win': returns[returns > 0].mean() if len(returns[returns > 0]) > 0 else 0,
356
+ 'avg_loss': returns[returns < 0].mean() if len(returns[returns < 0]) > 0 else 0,
357
+ 'profit_factor': abs(returns[returns > 0].sum() / (returns[returns < 0].sum() + 1e-10))
358
+ }
359
+
360
+
361
+ # ──────────────────────────────────────────────────────────────
362
+ # Plotly Charts
363
+ # ──────────────────────────────────────────────────────────────
364
+ def create_candlestick_chart(df: pd.DataFrame, ticker: str):
365
+ """Create interactive candlestick chart with indicators"""
366
+ fig = make_subplots(
367
+ rows=3, cols=1,
368
+ shared_xaxes=True,
369
+ vertical_spacing=0.05,
370
+ row_heights=[0.6, 0.2, 0.2],
371
+ subplot_titles=(f'{ticker} Price', 'Volume', 'RSI')
372
+ )
373
+
374
+ # Candlestick
375
+ fig.add_trace(go.Candlestick(
376
+ x=df.index,
377
+ open=df['Open'],
378
+ high=df['High'],
379
+ low=df['Low'],
380
+ close=df['Close'],
381
+ name='Price'
382
+ ), row=1, col=1)
383
+
384
+ # SMAs
385
+ fig.add_trace(go.Scatter(x=df.index, y=df['SMA_20'],
386
+ line=dict(color='orange', width=1), name='SMA 20'), row=1, col=1)
387
+ fig.add_trace(go.Scatter(x=df.index, y=df['SMA_50'],
388
+ line=dict(color='blue', width=1), name='SMA 50'), row=1, col=1)
389
+
390
+ # Bollinger Bands
391
+ fig.add_trace(go.Scatter(x=df.index, y=df['BB_Upper'],
392
+ line=dict(color='gray', width=1, dash='dash'),
393
+ name='BB Upper', opacity=0.5), row=1, col=1)
394
+ fig.add_trace(go.Scatter(x=df.index, y=df['BB_Lower'],
395
+ line=dict(color='gray', width=1, dash='dash'),
396
+ name='BB Lower', opacity=0.5), row=1, col=1)
397
+
398
+ # Volume
399
+ colors = ['green' if df['Close'].iloc[i] >= df['Open'].iloc[i] else 'red'
400
+ for i in range(len(df))]
401
+ fig.add_trace(go.Bar(x=df.index, y=df['Volume'],
402
+ marker_color=colors, name='Volume', opacity=0.7), row=2, col=1)
403
+
404
+ # RSI
405
+ fig.add_trace(go.Scatter(x=df.index, y=df['RSI'],
406
+ line=dict(color='purple', width=1.5), name='RSI'), row=3, col=1)
407
+ fig.add_hline(y=70, line_dash="dash", line_color="red", row=3, col=1)
408
+ fig.add_hline(y=30, line_dash="dash", line_color="green", row=3, col=1)
409
+ fig.add_hline(y=50, line_dash="dot", line_color="gray", row=3, col=1)
410
+
411
+ fig.update_layout(
412
+ title=f'{ticker} Technical Analysis Dashboard',
413
+ xaxis_rangeslider_visible=False,
414
+ height=800,
415
+ template='plotly_white',
416
+ showlegend=True,
417
+ legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1)
418
+ )
419
+
420
+ fig.update_yaxes(title_text="Price ($)", row=1, col=1)
421
+ fig.update_yaxes(title_text="Volume", row=2, col=1)
422
+ fig.update_yaxes(title_text="RSI", range=[0, 100], row=3, col=1)
423
+
424
+ return fig
425
+
426
+
427
+ def create_macd_chart(df: pd.DataFrame, ticker: str):
428
+ """Create MACD chart"""
429
+ fig = make_subplots(rows=1, cols=1)
430
+
431
+ fig.add_trace(go.Scatter(x=df.index, y=df['MACD'],
432
+ line=dict(color='blue', width=1.5), name='MACD'), row=1, col=1)
433
+ fig.add_trace(go.Scatter(x=df.index, y=df['MACD_Signal'],
434
+ line=dict(color='orange', width=1.5), name='Signal'), row=1, col=1)
435
+
436
+ colors = ['green' if val >= 0 else 'red' for val in df['MACD_Histogram']]
437
+ fig.add_trace(go.Bar(x=df.index, y=df['MACD_Histogram'],
438
+ marker_color=colors, name='Histogram', opacity=0.6), row=1, col=1)
439
+
440
+ fig.update_layout(
441
+ title=f'{ticker} MACD',
442
+ height=400,
443
+ template='plotly_white',
444
+ xaxis_rangeslider_visible=False
445
+ )
446
+
447
+ return fig
448
+
449
+
450
+ def create_return_distribution(returns: pd.Series, ticker: str):
451
+ """Create return distribution histogram"""
452
+ fig = go.Figure()
453
+
454
+ fig.add_trace(go.Histogram(
455
+ x=returns,
456
+ nbinsx=50,
457
+ name='Returns',
458
+ marker_color='steelblue',
459
+ opacity=0.7
460
+ ))
461
+
462
+ # Add normal overlay
463
+ x_range = np.linspace(returns.min(), returns.max(), 100)
464
+ mu, sigma = returns.mean(), returns.std()
465
+ normal_pdf = len(returns) * (x_range[1] - x_range[0]) * stats.norm.pdf(x_range, mu, sigma)
466
+ fig.add_trace(go.Scatter(x=x_range, y=normal_pdf,
467
+ mode='lines', line=dict(color='red', dash='dash'),
468
+ name='Normal'))
469
+
470
+ fig.update_layout(
471
+ title=f'{ticker} Return Distribution (vs Normal)',
472
+ xaxis_title='Daily Return',
473
+ yaxis_title='Frequency',
474
+ height=400,
475
+ template='plotly_white',
476
+ bargap=0.1
477
+ )
478
+
479
+ return fig
480
+
481
+
482
+ # ──────────────────────────────────────────────────────────────
483
+ # Portfolio Optimization
484
+ # ──────────────────────────────────────────────────────────────
485
+ def optimize_portfolio(tickers: list, period: str = "1y"):
486
+ """Mean-variance optimization for a portfolio"""
487
+ try:
488
+ # Fetch data
489
+ data = {}
490
+ for t in tickers:
491
+ t = t.strip().upper()
492
+ if not t:
493
+ continue
494
+ df, _ = fetch_stock_data(t, period=period)
495
+ if df is not None and len(df) > 30:
496
+ data[t] = df['Close']
497
+
498
+ if len(data) < 2:
499
+ return None, "Need at least 2 valid tickers for portfolio optimization."
500
+
501
+ # Align and compute returns
502
+ prices = pd.DataFrame(data)
503
+ prices = prices.dropna()
504
+ returns = prices.pct_change().dropna()
505
+
506
+ if len(returns) < 30:
507
+ return None, "Insufficient data after alignment."
508
+
509
+ # Expected returns and covariance
510
+ mu = returns.mean() * 252
511
+ sigma = returns.cov() * 252
512
+
513
+ # Optimize: max Sharpe
514
+ n = len(mu)
515
+
516
+ def neg_sharpe(weights):
517
+ weights = np.array(weights)
518
+ port_return = np.dot(weights, mu)
519
+ port_vol = np.sqrt(np.dot(weights.T, np.dot(sigma, weights)))
520
+ return -(port_return / (port_vol + 1e-10))
521
+
522
+ constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
523
+ bounds = tuple((0, 0.5) for _ in range(n)) # Max 50% per asset
524
+ x0 = np.ones(n) / n
525
+
526
+ result = minimize(neg_sharpe, x0, method='SLSQP', bounds=bounds, constraints=constraints)
527
+
528
+ optimal_weights = result.x
529
+
530
+ # Portfolio metrics
531
+ port_return = np.dot(optimal_weights, mu)
532
+ port_vol = np.sqrt(np.dot(optimal_weights.T, np.dot(sigma, optimal_weights)))
533
+ port_sharpe = port_return / (port_vol + 1e-10)
534
+
535
+ # Equal weight for comparison
536
+ eq_weights = np.ones(n) / n
537
+ eq_return = np.dot(eq_weights, mu)
538
+ eq_vol = np.sqrt(np.dot(eq_weights.T, np.dot(sigma, eq_weights)))
539
+ eq_sharpe = eq_return / (eq_vol + 1e-10)
540
+
541
+ return {
542
+ 'tickers': list(data.keys()),
543
+ 'optimal_weights': optimal_weights,
544
+ 'equal_weights': eq_weights,
545
+ 'optimal_return': port_return,
546
+ 'optimal_volatility': port_vol,
547
+ 'optimal_sharpe': port_sharpe,
548
+ 'equal_return': eq_return,
549
+ 'equal_volatility': eq_vol,
550
+ 'equal_sharpe': eq_sharpe,
551
+ 'annual_returns': mu,
552
+ 'covariance': sigma
553
+ }, None
554
+
555
+ except Exception as e:
556
+ return None, f"Optimization error: {str(e)}"
557
+
558
+
559
+ def create_efficient_frontier(opt_result: dict):
560
+ """Plot efficient frontier"""
561
+ mu = opt_result['annual_returns']
562
+ sigma = opt_result['covariance']
563
+ n = len(mu)
564
+
565
+ # Generate random portfolios
566
+ n_portfolios = 5000
567
+ weights = np.random.dirichlet(np.ones(n), n_portfolios)
568
+
569
+ port_returns = np.dot(weights, mu)
570
+ port_vols = np.array([np.sqrt(np.dot(w.T, np.dot(sigma, w))) for w in weights])
571
+ port_sharpes = port_returns / (port_vols + 1e-10)
572
+
573
+ fig = go.Figure()
574
+
575
+ # Scatter of random portfolios
576
+ fig.add_trace(go.Scatter(
577
+ x=port_vols, y=port_returns,
578
+ mode='markers',
579
+ marker=dict(
580
+ size=4,
581
+ color=port_sharpes,
582
+ colorscale='Viridis',
583
+ showscale=True,
584
+ colorbar=dict(title='Sharpe')
585
+ ),
586
+ name='Random Portfolios'
587
+ ))
588
+
589
+ # Optimal portfolio
590
+ fig.add_trace(go.Scatter(
591
+ x=[opt_result['optimal_volatility']],
592
+ y=[opt_result['optimal_return']],
593
+ mode='markers+text',
594
+ marker=dict(size=15, color='red', symbol='star'),
595
+ text=['Optimal'],
596
+ textposition='top center',
597
+ name='Optimal Portfolio'
598
+ ))
599
+
600
+ # Equal weight
601
+ fig.add_trace(go.Scatter(
602
+ x=[opt_result['equal_volatility']],
603
+ y=[opt_result['equal_return']],
604
+ mode='markers+text',
605
+ marker=dict(size=12, color='orange', symbol='diamond'),
606
+ text=['Equal Weight'],
607
+ textposition='bottom center',
608
+ name='Equal Weight'
609
+ ))
610
+
611
+ fig.update_layout(
612
+ title='Efficient Frontier (Monte Carlo Simulation)',
613
+ xaxis_title='Annual Volatility',
614
+ yaxis_title='Annual Return',
615
+ template='plotly_white',
616
+ height=500
617
+ )
618
+
619
+ return fig
620
+
621
+
622
+ # ──────────────────────────────────────────────────────────────
623
+ # Gradio UI Functions
624
+ # ──────────────────────────────────────────────────────────────
625
+ def analyze_ticker(ticker: str, period: str = "6mo"):
626
+ """Main analysis function for single ticker"""
627
+ ticker = ticker.strip().upper()
628
+ if not ticker:
629
+ return None, None, None, None, "Please enter a ticker symbol."
630
+
631
+ df, info = fetch_stock_data(ticker, period=period)
632
+
633
+ if df is None:
634
+ return None, None, None, None, info
635
+
636
+ # Compute indicators
637
+ df = compute_technical_indicators(df)
638
+ signals = generate_signals(df)
639
+ risk = compute_risk_metrics(df)
640
+
641
+ # Create charts
642
+ candlestick = create_candlestick_chart(df, ticker)
643
+ macd_chart = create_macd_chart(df, ticker)
644
+ returns_chart = create_return_distribution(df['Returns'].dropna(), ticker)
645
+
646
+ # Summary text
647
+ latest = df.iloc[-1]
648
+ prev = df.iloc[-2] if len(df) > 1 else latest
649
+
650
+ summary = f"""## {ticker} Analysis
651
+
652
+ **Current Price:** ${latest['Close']:.2f} | **Change:** {((latest['Close']/prev['Close']-1)*100):+.2f}%
653
+
654
+ ### Technical Signals
655
+ | Indicator | Value | Signal |
656
+ |-----------|-------|--------|
657
+ | RSI (14) | {latest['RSI']:.1f} | {'Oversold' if latest['RSI']<30 else 'Overbought' if latest['RSI']>70 else 'Neutral'} |
658
+ | MACD | {latest['MACD']:.3f} | {'Bullish' if latest['MACD']>latest['MACD_Signal'] else 'Bearish'} |
659
+ | Bollinger Position | {latest['BB_Position']:.1%} | {'Near upper band' if latest['BB_Position']>0.8 else 'Near lower band' if latest['BB_Position']<0.2 else 'Middle'} |
660
+ | VWAP Position | {'Above VWAP' if latest['Close']>latest['VWAP'] else 'Below VWAP'} | {'Bullish' if latest['Close']>latest['VWAP'] else 'Bearish'} |
661
+ | Volume vs Avg | {latest['Volume_Ratio']:.1f}x | {'Heavy' if latest['Volume_Ratio']>1.5 else 'Normal'} |
662
+
663
+ ### Composite Signal
664
+ **Direction:** {signals['direction']} | **Score:** {signals['composite_score']}/100 | **Confidence:** {signals['confidence']}%
665
+
666
+ ### Risk Metrics
667
+ | Metric | Value |
668
+ |--------|-------|
669
+ | Annualized Return | {risk.get('annual_return', 0)*100:.1f}% |
670
+ | Annualized Volatility | {risk.get('annual_volatility', 0)*100:.1f}% |
671
+ | Sharpe Ratio | {risk.get('sharpe_ratio', 0):.2f} |
672
+ | Sortino Ratio | {risk.get('sortino_ratio', 0):.2f} |
673
+ | Max Drawdown | {risk.get('max_drawdown', 0)*100:.1f}% |
674
+ | VaR (95%) | {risk.get('var_95_daily', 0)*100:.2f}% |
675
+ | CVaR (95%) | {risk.get('cvar_95_daily', 0)*100:.2f}% |
676
+ | Calmar Ratio | {risk.get('calmar_ratio', 0):.2f} |
677
+ | Win Rate | {risk.get('win_rate', 0)*100:.1f}% |
678
+ | Profit Factor | {risk.get('profit_factor', 0):.2f} |
679
+ | Skewness | {risk.get('skewness', 0):.2f} |
680
+ | Excess Kurtosis | {risk.get('excess_kurtosis', 0):.2f} |
681
+ """
682
+
683
+ return candlestick, macd_chart, returns_chart, summary, ""
684
+
685
+
686
+ def ai_analyze(ticker: str, period: str = "6mo"):
687
+ """AI-powered analysis using K2 Think V2"""
688
+ ticker = ticker.strip().upper()
689
+ if not ticker:
690
+ return "Please enter a ticker symbol."
691
+
692
+ df, info = fetch_stock_data(ticker, period=period)
693
+ if df is None:
694
+ return info
695
+
696
+ df = compute_technical_indicators(df)
697
+ signals = generate_signals(df)
698
+ risk = compute_risk_metrics(df)
699
+ latest = df.iloc[-1]
700
+
701
+ # Build data summary
702
+ data_summary = f"""
703
+ Ticker: {ticker}
704
+ Current Price: ${latest['Close']:.2f}
705
+ Period: {period}
706
+ Data Points: {len(df)}
707
+
708
+ Price Action:
709
+ - 20-day SMA: ${latest['SMA_20']:.2f}
710
+ - 50-day SMA: ${latest['SMA_50']:.2f}
711
+ - 52-week High: ${df['High'].max():.2f}
712
+ - 52-week Low: ${df['Low'].min():.2f}
713
+ - ATR (14): ${latest['ATR']:.2f}
714
+ """
715
+
716
+ technical_summary = f"""
717
+ RSI (14): {latest['RSI']:.1f} ({'oversold' if latest['RSI']<30 else 'overbought' if latest['RSI']>70 else 'neutral'})
718
+ MACD: {latest['MACD']:.3f} vs Signal: {latest['MACD_Signal']:.3f}
719
+ MACD Histogram: {latest['MACD_Histogram']:.3f}
720
+ Bollinger Band Position: {latest['BB_Position']:.1%}
721
+ Stochastic %K: {latest['Stoch_K']:.1f}
722
+ VWAP: ${latest['VWAP']:.2f}
723
+
724
+ Composite Signal Score: {signals['composite_score']}/100
725
+ Direction: {signals['direction']}
726
+ Confidence: {signals['confidence']}%
727
+
728
+ Risk Metrics:
729
+ Sharpe: {risk.get('sharpe_ratio', 0):.2f}
730
+ Volatility (ann): {risk.get('annual_volatility', 0)*100:.1f}%
731
+ Max Drawdown: {risk.get('max_drawdown', 0)*100:.1f}%
732
+ VaR 95%: {risk.get('var_95_daily', 0)*100:.2f}%
733
+ """
734
+
735
+ # Call K2 Think V2
736
+ client = K2ThinkClient()
737
+ analysis = client.analyze_market(ticker, data_summary, technical_summary)
738
+
739
+ return analysis
740
+
741
+
742
+ def analyze_portfolio(tickers_str: str, period: str = "1y"):
743
+ """Portfolio optimization and analysis"""
744
+ tickers = [t.strip().upper() for t in tickers_str.split(',') if t.strip()]
745
+
746
+ if len(tickers) < 2:
747
+ return None, None, "Please enter at least 2 tickers separated by commas."
748
+
749
+ # Optimize
750
+ result, error = optimize_portfolio(tickers, period)
751
+
752
+ if error:
753
+ return None, None, error
754
+
755
+ # Create efficient frontier
756
+ frontier = create_efficient_frontier(result)
757
+
758
+ # Weights comparison
759
+ weights_df = pd.DataFrame({
760
+ 'Ticker': result['tickers'],
761
+ 'Optimal Weight (%)': result['optimal_weights'] * 100,
762
+ 'Equal Weight (%)': result['equal_weights'] * 100
763
+ })
764
+
765
+ # Summary
766
+ summary = f"""## Portfolio Optimization Results
767
+
768
+ **Tickers:** {', '.join(result['tickers'])}
769
+
770
+ ### Optimal Portfolio (Max Sharpe)
771
+ | Metric | Value |
772
+ |--------|-------|
773
+ | Expected Annual Return | {result['optimal_return']*100:.1f}% |
774
+ | Expected Volatility | {result['optimal_volatility']*100:.1f}% |
775
+ | Sharpe Ratio | {result['optimal_sharpe']:.2f} |
776
+
777
+ ### Equal Weight Portfolio (Benchmark)
778
+ | Metric | Value |
779
+ |--------|-------|
780
+ | Expected Annual Return | {result['equal_return']*100:.1f}% |
781
+ | Expected Volatility | {result['equal_volatility']*100:.1f}% |
782
+ | Sharpe Ratio | {result['equal_sharpe']:.2f} |
783
+
784
+ ### Improvement
785
+ | | |
786
+ |-|-|
787
+ | Sharpe Improvement | {((result['optimal_sharpe']/result['equal_sharpe']-1)*100):+.1f}% |
788
+ | Return Improvement | {((result['optimal_return']/result['equal_return']-1)*100):+.1f}% |
789
+ | Risk Reduction | {((1-result['optimal_volatility']/result['equal_volatility'])*100):+.1f}% |
790
+
791
+ ### Optimal Weights
792
+ {weights_df.to_markdown(index=False)}
793
+ """
794
+
795
+ return frontier, weights_df, summary
796
+
797
+
798
+ def ai_portfolio_advice(tickers_str: str, period: str = "1y"):
799
+ """AI-powered portfolio advice via K2 Think V2"""
800
+ tickers = [t.strip().upper() for t in tickers_str.split(',') if t.strip()]
801
+
802
+ if len(tickers) < 2:
803
+ return "Please enter at least 2 tickers."
804
+
805
+ result, error = optimize_portfolio(tickers, period)
806
+ if error:
807
+ return error
808
+
809
+ # Build portfolio data
810
+ portfolio_data = f"""
811
+ Holdings:
812
+ {chr(10).join([f"- {t}: {w*100:.1f}%" for t, w in zip(result['tickers'], result['optimal_weights'])])}
813
+
814
+ Expected Return: {result['optimal_return']*100:.1f}%
815
+ Expected Volatility: {result['optimal_volatility']*100:.1f}%
816
+ Sharpe Ratio: {result['optimal_sharpe']:.2f}
817
+
818
+ Individual Asset Returns (annual):
819
+ {chr(10).join([f"- {t}: {r*100:.1f}%" for t, r in zip(result['tickers'], result['annual_returns'])])}
820
+ """
821
+
822
+ # Correlation matrix
823
+ corr = result['covariance']
824
+ for i in range(len(corr)):
825
+ corr.iloc[i, i] = np.nan # Hide diagonal
826
+
827
+ risk_metrics = f"""
828
+ Correlation Matrix (off-diagonal):
829
+ {corr.to_string()}
830
+
831
+ Highest pairwise correlation: {corr.stack().max():.2f}
832
+ Lowest pairwise correlation: {corr.stack().min():.2f}
833
+ Average correlation: {corr.stack().mean():.2f}
834
+ """
835
+
836
+ client = K2ThinkClient()
837
+ advice = client.portfolio_advice(portfolio_data, risk_metrics)
838
+
839
+ return advice
840
+
841
+
842
+ # ──────────────────────────────────────────────────────────────
843
+ # Gradio App Builder
844
+ # ──────────────────────────────────────────────────────────────
845
+ def build_app():
846
+ """Build the Gradio demo app"""
847
+
848
+ with gr.Blocks(
849
+ title="AlphaForge x K2 Think V2 β€” Elite Quant Trading",
850
+ theme=gr.themes.Soft(),
851
+ css="""
852
+ .main-title { text-align: center; font-size: 2.5em; font-weight: bold; color: #1a73e8; margin-bottom: 0.2em; }
853
+ .subtitle { text-align: center; font-size: 1.1em; color: #5f6368; margin-bottom: 1.5em; }
854
+ .api-badge { text-align: center; background: linear-gradient(90deg, #667eea, #764ba2);
855
+ color: white; padding: 8px 16px; border-radius: 20px; font-weight: 600; }
856
+ """
857
+ ) as demo:
858
+
859
+ # Header
860
+ gr.HTML("""
861
+ <div class="main-title">πŸ”₯ AlphaForge x K2 Think V2</div>
862
+ <div class="subtitle">Elite Quantitative Trading Platform powered by MBZUAI's State-of-the-Art Reasoning Model</div>
863
+ <div style="text-align: center; margin-bottom: 20px;">
864
+ <span class="api-badge">πŸ€– K2 Think V2 API Active</span>
865
+ <span style="margin-left: 10px;" class="api-badge">πŸ“Š Real-Time Market Data</span>
866
+ <span style="margin-left: 10px;" class="api-badge">🎯 AI-Powered Alpha</span>
867
+ </div>
868
+ """)
869
+
870
+ # ── TAB 1: Single Stock Analysis ──
871
+ with gr.Tab("πŸ“ˆ Single Stock Analysis"):
872
+ with gr.Row():
873
+ with gr.Column(scale=1):
874
+ ticker_input = gr.Textbox(
875
+ label="Stock Ticker",
876
+ placeholder="e.g., AAPL, TSLA, NVDA, SPY",
877
+ value="AAPL"
878
+ )
879
+ period_input = gr.Dropdown(
880
+ label="Time Period",
881
+ choices=["1mo", "3mo", "6mo", "1y", "2y", "5y"],
882
+ value="6mo"
883
+ )
884
+ analyze_btn = gr.Button("πŸ” Analyze Stock", variant="primary")
885
+ ai_btn = gr.Button("πŸ€– AI Deep Analysis (K2 Think V2)", variant="secondary")
886
+
887
+ with gr.Column(scale=2):
888
+ summary_output = gr.Markdown(label="Analysis Summary")
889
+
890
+ with gr.Row():
891
+ candlestick_plot = gr.Plot(label="Price & Technicals")
892
+ macd_plot = gr.Plot(label="MACD")
893
+
894
+ with gr.Row():
895
+ returns_plot = gr.Plot(label="Return Distribution")
896
+ ai_output = gr.Textbox(
897
+ label="K2 Think V2 AI Analysis",
898
+ lines=20,
899
+ max_lines=30,
900
+ autoscroll=True
901
+ )
902
+
903
+ error_output = gr.Textbox(label="Status", visible=False)
904
+
905
+ # Bind buttons
906
+ analyze_btn.click(
907
+ fn=analyze_ticker,
908
+ inputs=[ticker_input, period_input],
909
+ outputs=[candlestick_plot, macd_plot, returns_plot, summary_output, error_output]
910
+ )
911
+
912
+ ai_btn.click(
913
+ fn=ai_analyze,
914
+ inputs=[ticker_input, period_input],
915
+ outputs=[ai_output]
916
+ )
917
+
918
+ # ── TAB 2: Portfolio Optimization ──
919
+ with gr.Tab("πŸ’Ό Portfolio Optimizer"):
920
+ with gr.Row():
921
+ with gr.Column(scale=1):
922
+ portfolio_input = gr.Textbox(
923
+ label="Portfolio Tickers (comma-separated)",
924
+ placeholder="e.g., AAPL, MSFT, GOOGL, AMZN, NVDA",
925
+ value="AAPL, MSFT, GOOGL, AMZN, NVDA"
926
+ )
927
+ portfolio_period = gr.Dropdown(
928
+ label="Lookback Period",
929
+ choices=["6mo", "1y", "2y", "3y"],
930
+ value="1y"
931
+ )
932
+ optimize_btn = gr.Button("🎯 Optimize Portfolio", variant="primary")
933
+ ai_portfolio_btn = gr.Button("πŸ€– AI Portfolio Advice (K2 Think V2)", variant="secondary")
934
+
935
+ with gr.Column(scale=2):
936
+ portfolio_summary = gr.Markdown(label="Optimization Results")
937
+
938
+ with gr.Row():
939
+ frontier_plot = gr.Plot(label="Efficient Frontier")
940
+ weights_table = gr.DataFrame(label="Optimal Weights")
941
+
942
+ ai_portfolio_output = gr.Textbox(
943
+ label="K2 Think V2 Portfolio Advice",
944
+ lines=20,
945
+ max_lines=30
946
+ )
947
+
948
+ optimize_btn.click(
949
+ fn=analyze_portfolio,
950
+ inputs=[portfolio_input, portfolio_period],
951
+ outputs=[frontier_plot, weights_table, portfolio_summary]
952
+ )
953
+
954
+ ai_portfolio_btn.click(
955
+ fn=ai_portfolio_advice,
956
+ inputs=[portfolio_input, portfolio_period],
957
+ outputs=[ai_portfolio_output]
958
+ )
959
+
960
+ # ── TAB 3: AI Chat ──
961
+ with gr.Tab("πŸ’¬ K2 Think V2 Chat"):
962
+ gr.Markdown("## Direct Chat with K2 Think V2\nAsk any financial question, strategy idea, or market analysis.")
963
+
964
+ chat_input = gr.Textbox(
965
+ label="Your Question",
966
+ placeholder="e.g., 'Analyze the bull case for AI stocks in 2025' or 'Explain pairs trading with a real example'",
967
+ lines=3
968
+ )
969
+ chat_temp = gr.Slider(label="Temperature", minimum=0.0, maximum=1.0, value=0.5, step=0.1)
970
+ chat_btn = gr.Button("πŸš€ Ask K2 Think V2", variant="primary")
971
+ chat_output = gr.Textbox(label="K2 Think V2 Response", lines=25, max_lines=40)
972
+
973
+ def direct_chat(question, temperature):
974
+ if not question.strip():
975
+ return "Please enter a question."
976
+ client = K2ThinkClient()
977
+ messages = [{"role": "user", "content": question}]
978
+ return client.chat(messages, temperature=temperature, max_tokens=4096)
979
+
980
+ chat_btn.click(
981
+ fn=direct_chat,
982
+ inputs=[chat_input, chat_temp],
983
+ outputs=[chat_output]
984
+ )
985
+
986
+ # ── TAB 4: About ──
987
+ with gr.Tab("ℹ️ About"):
988
+ gr.Markdown("""
989
+ ## AlphaForge x K2 Think V2
990
+
991
+ Built for the **Build with K2 Think V2 Challenge** by MBZUAI.
992
+
993
+ ### What This Demo Shows
994
+
995
+ 1. **Real-Time Market Data** β€” Fetch live stock prices, volume, OHLCV from Yahoo Finance
996
+ 2. **Technical Analysis** β€” 14+ indicators: RSI, MACD, Bollinger, VWAP, Stochastic, ATR
997
+ 3. **Alpha Signal Generation** β€” Composite scoring combining trend, momentum, volatility, volume
998
+ 4. **Risk Metrics** β€” Sharpe, Sortino, VaR, CVaR, Calmar, max drawdown, skewness, kurtosis
999
+ 5. **Portfolio Optimization** β€” Mean-variance optimization with efficient frontier visualization
1000
+ 6. **AI-Powered Analysis** β€” K2 Think V2 (MBZUAI's SOTA reasoning model) provides deep chain-of-thought analysis
1001
+
1002
+ ### Architecture
1003
+
1004
+ ```
1005
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
1006
+ β”‚ yfinance │────▢│ AlphaForge │────▢│ K2 Think V2 β”‚
1007
+ β”‚ (market data) β”‚ β”‚ (quant signals) β”‚ β”‚ (AI reasoning) β”‚
1008
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
1009
+ ```
1010
+
1011
+ ### K2 Think V2 API
1012
+
1013
+ - **Model**: MBZUAI-IFM/K2-Think-v2
1014
+ - **Provider**: MBZUAI (Mohamed bin Zayed University of AI)
1015
+ - **Features**: Chain-of-thought reasoning, deep financial analysis, multi-step logic
1016
+
1017
+ ### Technologies
1018
+
1019
+ - **Gradio** β€” Interactive web UI
1020
+ - **yfinance** β€” Real-time market data
1021
+ - **Plotly** β€” Interactive charts
1022
+ - **K2 Think V2** β€” AI reasoning and analysis
1023
+
1024
+ ### Links
1025
+
1026
+ - [AlphaForge Quant System](https://huggingface.co/Premchan369/alphaforge-quant-system) β€” Full open-source quant platform (25 modules)
1027
+ - [Build with K2 Think V2](https://build.k2think.ai/) β€” Official challenge page
1028
+ - [MBZUAI](https://mbzuai.ac.ae/) β€” Mohamed bin Zayed University of AI
1029
+
1030
+ ---
1031
+
1032
+ *Built by Premchan | Built with K2 Think V2 Challenge*
1033
+ """)
1034
+
1035
+ return demo
1036
+
1037
+
1038
+ # ──────────────────────────────────────────────────────────────
1039
+ # Main Entry Point
1040
+ # ──────────────────────────────────────────────────────────────
1041
+ if __name__ == "__main__":
1042
+ demo = build_app()
1043
+
1044
+ # For HuggingFace Spaces
1045
+ demo.queue().launch(
1046
+ server_name="0.0.0.0",
1047
+ server_port=7860,
1048
+ share=True,
1049
+ show_error=True
1050
+ )