Premchan369 commited on
Commit
40b6bd4
·
verified ·
1 Parent(s): 097d42f

V2.0: Multi-market, dark finance UI, options, pairs, macro, enhanced AI prompts

Browse files
Files changed (1) hide show
  1. app.py +226 -500
app.py CHANGED
@@ -1,8 +1,8 @@
1
- """AlphaForge x K2 Think V2 Institutional-Grade Quantitative Analysis Platform
2
 
3
  Multi-market support: US, EU, Asia, Crypto, Forex, Commodities
4
  Finance-themed UI with dark mode, professional color scheme
5
- Enhanced features: Options pricing, Pairs Trading, Backtest, Macro Analysis
6
  Powered by MBZUAI K2 Think V2 reasoning model
7
 
8
  API Key: set via K2_API_KEY environment variable
@@ -10,55 +10,25 @@ API Key: set via K2_API_KEY environment variable
10
  import os, json, traceback, warnings, math, random
11
  warnings.filterwarnings('ignore')
12
 
13
- # Core imports with graceful fallback
14
- _import_errors = []
15
  try:
16
  import gradio as gr
17
- except ImportError as e:
18
- _import_errors.append(f"gradio: {e}")
19
- gr = None
20
- try:
21
  import requests
22
- except ImportError as e:
23
- _import_errors.append(f"requests: {e}")
24
- requests = None
25
- try:
26
  import yfinance as yf
27
- except ImportError as e:
28
- _import_errors.append(f"yfinance: {e}")
29
- yf = None
30
- try:
31
  import pandas as pd
32
- pd.set_option('future.no_silent_downcasting', True)
33
- except ImportError as e:
34
- _import_errors.append(f"pandas: {e}")
35
- pd = None
36
- try:
37
  import numpy as np
38
- except ImportError as e:
39
- _import_errors.append(f"numpy: {e}")
40
- np = None
41
- try:
42
  import plotly.graph_objects as go
43
  from plotly.subplots import make_subplots
44
  PLOTLY_OK = True
45
  except ImportError as e:
46
- _import_errors.append(f"plotly: {e}")
47
- PLOTLY_OK = False
48
- go = None
49
- make_subplots = None
50
- from datetime import datetime, timedelta
51
 
52
- # ═══════════════════════════════════════════════════════════════
53
  # CONFIG
54
- # ═══════════════════════════════════════════════════════════════
55
  K2_API_KEY = os.environ.get("K2_API_KEY", "")
56
  K2_BASE_URL = "https://api.k2think.ai/v1/chat/completions"
57
  K2_MODEL = "MBZUAI-IFM/K2-Think-v2"
58
 
59
- # ═══════════════════════════════════════════════════════════════
60
- # K2 THINK V2 CLIENT — bulletproof
61
- # ═══════════════════════════════════════════════════════════════
62
  class K2ThinkClient:
63
  def __init__(self):
64
  self.api_key = K2_API_KEY
@@ -66,17 +36,8 @@ class K2ThinkClient:
66
  self.base_url = K2_BASE_URL
67
 
68
  def chat(self, messages, temperature=0.7, max_tokens=4096):
69
- if not self.available or requests is None:
70
- return """⚠️ **K2 Think V2 API Not Configured**
71
-
72
- To enable AI-powered analysis, configure the API key:
73
- 1. Go to **Space Settings** → **Repository secrets**
74
- 2. Click **New secret**
75
- 3. Name: `K2_API_KEY`
76
- 4. Value: `IFM-4SpQ0qEg0Wlsw04O`
77
- 5. Click **Save**, then **Factory Rebuild**
78
-
79
- All technical analysis, charts, and risk metrics work without the API!"""
80
 
81
  payload = {"model": K2_MODEL, "messages": messages, "temperature": temperature,
82
  "max_tokens": max_tokens, "stream": False}
@@ -91,10 +52,10 @@ All technical analysis, charts, and risk metrics work without the API!"""
91
  return j['choices'][0]['message']['content']
92
  return f"⚠️ Unexpected format: {json.dumps(j, indent=2)[:400]}"
93
  except requests.exceptions.Timeout:
94
- return "⏱️ Timeout after 120s. API may be under high load. Try again."
95
  except requests.exceptions.HTTPError as e:
96
  if e.response.status_code == 401:
97
- return "🔐 Auth failed. Check K2_API_KEY secret in Space Settings."
98
  elif e.response.status_code == 429:
99
  return "🚦 Rate limited. Wait a moment."
100
  return f"🔴 HTTP {e.response.status_code}: {str(e)[:200]}"
@@ -102,8 +63,8 @@ All technical analysis, charts, and risk metrics work without the API!"""
102
  return f"🔴 Error: {str(e)[:300]}"
103
 
104
  def analyze_market(self, ticker, market, data_summary, tech_summary, timeframe):
105
- prompt = f"""You are an elite quantitative analyst at a top hedge fund (Two Sigma / Jane Street level).
106
- Analyze the following market data with deep chain-of-thought reasoning.
107
 
108
  ## Asset Information
109
  - **Ticker**: {ticker}
@@ -120,42 +81,31 @@ Analyze the following market data with deep chain-of-thought reasoning.
120
  Provide exactly these sections:
121
 
122
  ### 1. Executive Summary (3 bullets)
123
- - Most critical finding
124
- - Key risk factor
125
- - Immediate catalyst to watch
126
-
127
  ### 2. Technical Analysis
128
- - Interpret RSI, MACD, Bollinger Bands in context of current price action
129
  - Identify support/resistance levels from SMAs and VWAP
130
- - Note any divergence signals
131
-
132
  ### 3. Risk Assessment
133
  - Volatility regime (low/normal/high)
134
- - Tail risk estimate (based on VaR, skewness, kurtosis)
135
- - Correlation risk if part of a broader portfolio
136
-
137
  ### 4. Alpha Signal
138
  - Direction: BULLISH / NEUTRAL / BEARISH
139
  - Confidence: X%
140
- - Time horizon: Short-term (1-4 weeks) / Medium-term (1-3 months) / Long-term (3-12 months)
141
  - Key conviction drivers
142
-
143
  ### 5. Trade Recommendation
144
  - Entry price / zone
145
- - Stop-loss (tight vs wide reasoning)
146
- - Target 1 (conservative)
147
- - Target 2 (aggressive)
148
- - Position sizing suggestion (% of portfolio)
149
-
150
  ### 6. Catalyst Calendar
151
- - Next 7 days: earnings, macro data, Fed speakers
152
- - Next 30 days: options expiration, sector events
153
-
154
  ### 7. Contrarian View
155
  - What would make this signal wrong?
156
  - Alternative scenario with probability
157
 
158
- Think step-by-step. Reference specific numbers from the data."""
159
  return self.chat([{"role": "user", "content": prompt}], temperature=0.2, max_tokens=4096)
160
 
161
  def portfolio_advice(self, portfolio_data, corr_data, risk_metrics, market_context):
@@ -175,40 +125,16 @@ Think step-by-step. Reference specific numbers from the data."""
175
 
176
  ## Deliverables
177
 
178
- ### 1. Portfolio Health Score (0-100)
179
- Grade with letter (A+ to F) and justify
180
-
181
  ### 2. Concentration Risk
182
- - Single-name exposure limits
183
- - Sector/style drift
184
- - Geographic concentration
185
-
186
  ### 3. Correlation Risk Matrix
187
- - Hidden correlated bets
188
- - Diversification quality score
189
- - Tail correlation concern
190
-
191
  ### 4. Rebalancing Roadmap
192
  - Specific weight adjustments with %
193
  - Timeline: immediate / 1 week / 1 month
194
- - Tax/transaction cost considerations
195
-
196
  ### 5. Hedging Strategy
197
- - Options structure for tail protection
198
- - ETF hedge ratios
199
- - Cost estimate ($ and basis points)
200
-
201
  ### 6. Expected Return & Risk (Forward 12M)
202
- | Metric | Point Estimate | Range |
203
- |--------|---------------|-------|
204
- | Annual Return | X% | [Y%, Z%] |
205
- | Volatility | X% | [Y%, Z%] |
206
- | Sharpe | X.X | [Y, Z] |
207
- | Max Drawdown | -X% | [-Y%, -Z%] |
208
- | 95% VaR (1M) | -X% | — |
209
-
210
  ### 7. Scenario Analysis
211
- - Bull case (20% probability): what happens, portfolio impact
212
  - Base case (50% probability)
213
  - Bear case (20% probability)
214
  - Tail case (10% probability)
@@ -225,98 +151,40 @@ Use quantitative reasoning throughout."""
225
  ## Deliverables
226
 
227
  ### 1. Macro Regime Classification
228
- - Current regime (growth/inflation quadrant)
229
- - Regime stability (stable vs transitioning)
230
- - Historical analog (which past period rhymes)
231
-
232
  ### 2. Cross-Asset Implications
233
- - Equities: sector rotation, style factor
234
- - Fixed Income: curve shape, credit spread
235
- - FX: carry trade, safe haven flows
236
- - Commodities: supply/demand, inflation hedge
237
- - Crypto: risk-on/risk-off sensitivity
238
-
239
  ### 3. Trade Ideas (3 concrete setups)
240
  Each with: instrument, direction, entry, stop, target, conviction %, time horizon
241
-
242
  ### 4. Risk Factors
243
- - Top 3 risks that could invalidate thesis
244
- - Probability and impact matrix
245
- - Hedging suggestion
246
 
247
  Think like a macro PM."""
248
  return self.chat([{"role": "user", "content": prompt}], temperature=0.3, max_tokens=4096)
249
-
250
- def options_analysis(self, option_data, stock_data):
251
- prompt = f"""You are an exotics trader at Citadel / SIG analyzing options.
252
-
253
- ## Option Parameters
254
- {option_data}
255
-
256
- ## Underlying Stock Data
257
- {stock_data}
258
-
259
- ## Deliverables
260
 
261
- ### 1. Greeks Analysis
262
- - Delta hedge ratio
263
- - Gamma risk (pin risk near expiry)
264
- - Theta decay per day
265
- - Vega sensitivity to vol moves
266
- - Vomma / Vanna if relevant
267
-
268
- ### 2. Pricing Assessment
269
- - Is this option cheap/fair/expensive vs historical vol?
270
- - Implied vs realized vol spread
271
- - Term structure shape
272
-
273
- ### 3. Strategy Recommendation
274
- - Best structure for current view (vertical, butterfly, iron condor, etc.)
275
- - Expected P/L at expiry (visual table)
276
- - Break-even points
277
- - Max gain / max loss
278
-
279
- ### 4. Scenario Analysis
280
- | Stock Price at Expiry | P/L | Probability (approx) |
281
- |----------------------|-----|----------------------|
282
- | -20% | $X | Y% |
283
- | -10% | $X | Y% |
284
- | 0% | $X | Y% |
285
- | +10% | $X | Y% |
286
- | +20% | $X | Y% |
287
-
288
- Risk-adjusted expected return calculation."""
289
- return self.chat([{"role": "user", "content": prompt}], temperature=0.3, max_tokens=4096)
290
-
291
- # ═══════════════════════════════════════════════════════════════
292
- # MARKET DATA — multi-market support
293
- # ═══════════════════════════════════════════════════════════════
294
  MARKET_PRESETS = {
295
  "🇺🇸 US Equities": {"suffix": "", "examples": "AAPL, TSLA, NVDA, SPY, QQQ, META, AMZN, GOOGL"},
296
- "🇪🇺 European Equities": {"suffix": ".PA", "examples": "AIR.PA, SAN.PA, TTE.PA, OR.PA, MC.PA, ASML.AS"},
297
- "🇬🇧 UK Equities": {"suffix": ".L", "examples": "AZN.L, SHEL.L, BP.L, ULVR.L, RIO.L, GSK.L"},
298
- "🇩🇪 German Equities": {"suffix": ".DE", "examples": "SAP.DE, SIE.DE, ALV.DE, BAS.DE, BMW.DE, MBG.DE"},
299
- "🇯🇵 Japanese Equities": {"suffix": ".T", "examples": "7203.T, 9984.T, 6861.T, 6758.T, 8306.T"},
300
  "🇨🇳 Chinese Equities": {"suffix": ".HK", "examples": "0700.HK, 9988.HK, 3690.HK, 1810.HK"},
301
- "🇮🇳 Indian Equities": {"suffix": ".NS", "examples": "RELIANCE.NS, TCS.NS, INFY.NS, HDFCBANK.NS"},
302
- "🪙 Crypto": {"suffix": "", "examples": "BTC-USD, ETH-USD, SOL-USD, XRP-USD, ADA-USD, DOGE-USD"},
303
- "💱 Forex Majors": {"suffix": "=X", "examples": "EURUSD=X, GBPUSD=X, USDJPY=X, AUDUSD=X, USDCAD=X"},
304
- "🥇 Commodities": {"suffix": "", "examples": "GC=F, SI=F, CL=F, NG=F, ZC=F, ZW=F"},
305
- "📊 Indices": {"suffix": "", "examples": "^GSPC, ^DJI, ^IXIC, ^FTSE, ^N225, ^HSI"},
306
  }
307
 
308
  def fetch_data(ticker, period="6mo", interval="1d"):
309
- if yf is None:
310
- return None, "yfinance not available"
311
  try:
312
  stock = yf.Ticker(ticker.upper().strip())
313
  df = stock.history(period=period, interval=interval)
314
  if df.empty:
315
- return None, f"No data for '{ticker}'. Try: {random.choice(list(MARKET_PRESETS.values()))['examples']}"
316
  info = stock.info
317
- return df, info
318
  except Exception as e:
319
- return None, f"Error fetching '{ticker}': {str(e)[:200]}"
320
 
321
  def calc_indicators(df):
322
  df = df.copy()
@@ -355,7 +223,6 @@ def calc_indicators(df):
355
  df['Stoch_D'] = df['Stoch_K'].rolling(3).mean()
356
  df['VM'] = df['Volume'].rolling(20).mean()
357
  df['VR'] = df['Volume']/(df['VM']+1e-10)
358
- # ADX
359
  plus_dm = df['High'].diff()
360
  minus_dm = df['Low'].diff()
361
  plus_dm[plus_dm<0] = 0
@@ -366,9 +233,7 @@ def calc_indicators(df):
366
  df['minus_DI'] = 100 * (minus_dm.ewm(alpha=1/14, adjust=False).mean() / atr_smooth)
367
  dx = 100 * np.abs(df['plus_DI']-df['minus_DI'])/(df['plus_DI']+df['minus_DI']+1e-10)
368
  df['ADX'] = dx.ewm(alpha=1/14, adjust=False).mean()
369
- # OBV
370
  df['OBV'] = (np.sign(df['Close'].diff())*df['Volume']).cumsum()
371
- # MFI
372
  tp_r = (df['High']+df['Low']+df['Close'])/3
373
  tp_diff = tp_r.diff()
374
  pos_flow = tp_r.where(tp_diff>0,0)*df['Volume']
@@ -376,7 +241,6 @@ def calc_indicators(df):
376
  mfi_pos = pos_flow.rolling(14).sum()
377
  mfi_neg = neg_flow.rolling(14).sum()
378
  df['MFI'] = 100 - (100/(1+mfi_pos/(mfi_neg+1e-10)))
379
- # Ichimoku
380
  df['ICH_tenkan'] = (df['High'].rolling(9).max()+df['Low'].rolling(9).min())/2
381
  df['ICH_kijun'] = (df['High'].rolling(26).max()+df['Low'].rolling(26).min())/2
382
  df['ICH_senkou_A'] = ((df['ICH_tenkan']+df['ICH_kijun'])/2).shift(26)
@@ -401,14 +265,12 @@ def calc_risk(df):
401
  cv95 = r[r<=v95].mean() if len(r[r<=v95])>0 else v95
402
  cv99 = r[r<=v99].mean() if len(r[r<=v99])>0 else v99
403
  ca = ar/(abs(md)+1e-10)
404
- # Rolling metrics
405
  roll_sharpe = (r.rolling(63).mean()*252)/(r.rolling(63).std()*np.sqrt(252)+1e-10)
406
  return {'ar':ar,'av':av,'sh':sh,'so':so,'md':md,'v95':v95,'v99':v99,
407
  'cv95':cv95,'cv99':cv99,'ca':ca,'sk':r.skew(),'ku':r.kurtosis(),
408
  'wr':(r>0).mean(),'pf':abs(r[r>0].sum()/(r[r<0].sum()+1e-10)),
409
  'avg_win':r[r>0].mean() if len(r[r>0])>0 else 0,
410
  'avg_loss':r[r<0].mean() if len(r[r<0])>0 else 0,
411
- 'consec_wins':0,'consec_losses':0,
412
  'roll_sharpe':roll_sharpe.iloc[-1] if len(roll_sharpe.dropna())>0 else 0,
413
  'vol_regime':'low' if av<0.15 else 'normal' if av<0.30 else 'high'}
414
 
@@ -449,12 +311,10 @@ def calc_signals(df):
449
  s['adx_trend'] = 'strong trend'
450
  elif l['ADX'] > 20:
451
  s['adx_trend'] = 'trending'
452
- # Ichimoku
453
  if l['Close'] > l['ICH_senkou_A'] and l['Close'] > l['ICH_senkou_B']:
454
  s['ichimoku'] = 'bullish cloud'
455
  elif l['Close'] < l['ICH_senkou_A'] and l['Close'] < l['ICH_senkou_B']:
456
  s['ichimoku'] = 'bearish cloud'
457
- # Score
458
  sc = 50
459
  if 'bullish' in s['trend']: sc += 20
460
  if 'bearish' in s['trend']: sc -= 20
@@ -474,30 +334,20 @@ def calc_signals(df):
474
  return s
475
 
476
  def make_candlestick(df, ticker, market):
477
- if not PLOTLY_OK:
478
- return None
479
  fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.03,
480
  row_heights=[0.55, 0.25, 0.20],
481
  subplot_titles=(f'{ticker} ({market})', 'Volume + VWAP', 'RSI'))
482
- # Candlestick
483
  fig.add_trace(go.Candlestick(x=df.index, open=df['Open'], high=df['High'],
484
  low=df['Low'], close=df['Close'], name='Price',
485
  increasing_line_color='#00C853', decreasing_line_color='#FF5252'), row=1, col=1)
486
- # SMAs
487
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA20'], line=dict(color='#FF9800', width=1), name='SMA20'), row=1, col=1)
488
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA50'], line=dict(color='#2196F3', width=1), name='SMA50'), row=1, col=1)
489
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA200'], line=dict(color='#9C27B0', width=1.5, dash='dash'), name='SMA200'), row=1, col=1)
490
- # BB
491
  fig.add_trace(go.Scatter(x=df.index, y=df['BBU'], line=dict(color='gray', width=0.8, dash='dash'), name='BB+', opacity=0.4), row=1, col=1)
492
  fig.add_trace(go.Scatter(x=df.index, y=df['BBL'], line=dict(color='gray', width=0.8, dash='dash'), name='BB-', opacity=0.4), row=1, col=1)
493
- # Ichimoku
494
- fig.add_trace(go.Scatter(x=df.index, y=df['ICH_senkou_A'], fill=None, mode='lines', line=dict(color='green', width=0.5), name='Senkou A'), row=1, col=1)
495
- fig.add_trace(go.Scatter(x=df.index, y=df['ICH_senkou_B'], fill='tonexty', fillcolor='rgba(46,125,50,0.1)', mode='lines', line=dict(color='red', width=0.5), name='Senkou B'), row=1, col=1)
496
- # Volume
497
  colors = ['#00C853' if df['Close'].iloc[i]>=df['Open'].iloc[i] else '#FF5252' for i in range(len(df))]
498
  fig.add_trace(go.Bar(x=df.index, y=df['Volume'], marker_color=colors, name='Volume', opacity=0.7), row=2, col=1)
499
  fig.add_trace(go.Scatter(x=df.index, y=df['VM'], line=dict(color='#FF9800', width=1), name='Vol MA20'), row=2, col=1)
500
- # RSI
501
  fig.add_trace(go.Scatter(x=df.index, y=df['RSI'], line=dict(color='#9C27B0', width=1.5), fill='tozeroy', fillcolor='rgba(156,39,176,0.1)'), row=3, col=1)
502
  fig.add_hline(y=70, line_dash="dash", line_color="#FF5252", row=3, col=1)
503
  fig.add_hline(y=30, line_dash="dash", line_color="#00C853", row=3, col=1)
@@ -512,7 +362,6 @@ def make_candlestick(df, ticker, market):
512
  return fig
513
 
514
  def make_macd(df, ticker):
515
- if not PLOTLY_OK: return None
516
  fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05,
517
  row_heights=[0.6, 0.4], subplot_titles=('MACD', 'Histogram'))
518
  fig.add_trace(go.Scatter(x=df.index, y=df['MACD'], line=dict(color='#2196F3', width=1.5), name='MACD'), row=1, col=1)
@@ -524,84 +373,65 @@ def make_macd(df, ticker):
524
  return fig
525
 
526
  def make_stoch(df, ticker):
527
- if not PLOTLY_OK: return None
528
  fig = go.Figure()
529
  fig.add_trace(go.Scatter(x=df.index, y=df['Stoch_K'], line=dict(color='#2196F3', width=1.5), name='%K'))
530
  fig.add_trace(go.Scatter(x=df.index, y=df['Stoch_D'], line=dict(color='#FF9800', width=1.5), name='%D'))
531
  fig.add_hline(y=80, line_dash="dash", line_color="#FF5252")
532
  fig.add_hline(y=20, line_dash="dash", line_color="#00C853")
533
- fig.update_layout(title=f'{ticker} Stochastic Oscillator', template='plotly_dark',
534
- height=400, paper_bgcolor='#0d1117', plot_bgcolor='#161b22',
535
- font=dict(color='#e6edf3'))
536
  return fig
537
 
538
  def make_vol(df, ticker):
539
- if not PLOTLY_OK: return None
540
  fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05,
541
  row_heights=[0.6, 0.4], subplot_titles=('ATR %', 'Volume Ratio'))
542
  fig.add_trace(go.Scatter(x=df.index, y=df['ATR_pct'], line=dict(color='#FF9800', width=1.5), fill='tozeroy'), row=1, col=1)
543
  fig.add_trace(go.Scatter(x=df.index, y=df['VR'], line=dict(color='#9C27B0', width=1.5)), row=2, col=1)
544
  fig.add_hline(y=1.0, line_dash="dash", line_color="gray", row=2, col=1)
545
- fig.update_layout(title=f'{ticker} Volatility & Volume', template='plotly_dark',
546
- height=500, paper_bgcolor='#0d1117', plot_bgcolor='#161b22',
547
- font=dict(color='#e6edf3'))
548
  return fig
549
 
550
  def make_adx(df, ticker):
551
- if not PLOTLY_OK: return None
552
  fig = go.Figure()
553
  fig.add_trace(go.Scatter(x=df.index, y=df['plus_DI'], line=dict(color='#00C853', width=1), name='+DI'))
554
  fig.add_trace(go.Scatter(x=df.index, y=df['minus_DI'], line=dict(color='#FF5252', width=1), name='-DI'))
555
  fig.add_trace(go.Scatter(x=df.index, y=df['ADX'], line=dict(color='#2196F3', width=2), name='ADX'))
556
  fig.add_hline(y=25, line_dash="dash", line_color="gray")
557
- fig.update_layout(title=f'{ticker} ADX Trend Strength', template='plotly_dark',
558
- height=400, paper_bgcolor='#0d1117', plot_bgcolor='#161b22',
559
- font=dict(color='#e6edf3'))
560
  return fig
561
 
562
  def make_dist(r, ticker):
563
- if not PLOTLY_OK: return None
564
  fig = go.Figure()
565
  fig.add_trace(go.Histogram(x=r, nbinsx=50, marker_color='#2196F3', opacity=0.7, name='Returns'))
566
  mu, sig = r.mean(), r.std()
567
- x_range = np.linspace(r.min(), r.max(), 100)
568
- try:
569
- from scipy import stats as sp_stats
570
- normal_pdf = len(r)*(x_range[1]-x_range[0])*sp_stats.norm.pdf(x_range, mu, sig)
571
- fig.add_trace(go.Scatter(x=x_range, y=normal_pdf, mode='lines',
572
- line=dict(color='#FF5252', dash='dash'), name='Normal'))
573
- except:
574
- pass
575
  fig.add_vline(x=mu, line_color='#00C853', line_dash='dash', annotation_text=f'Mean: {mu*100:.2f}%')
576
- fig.add_vline(x=np.percentile(r,5), line_color='#FF5252', line_dash='dot', annotation_text=f'VaR95')
577
- fig.update_layout(title=f'{ticker} Return Distribution', xaxis_title='Daily Return',
578
- yaxis_title='Frequency', height=400, template='plotly_dark',
579
  paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
580
  return fig
581
 
582
- # ═══════════════════════════════════════════════════════════════
583
- # PORTFOLIO OPTIMIZATION
584
- # ═══════════════════════════════════════════════════════════════
585
  def optimize_portfolio(tickers, period="1y"):
586
- if yf is None or pd is None or np is None:
587
- return None, None, "Libraries unavailable."
588
  ts = [t.strip().upper() for t in tickers.split(',') if t.strip()]
589
  if len(ts) < 2:
590
- return None, None, "Enter at least 2 tickers."
591
  data = {}
592
  errs = []
593
  for t in ts:
594
- df, err = fetch_data(t, period)
595
  if err:
596
  errs.append(err)
597
  elif df is not None and len(df) > 30:
598
  data[t] = df['Close']
599
  if len(data) < 2:
600
- return None, None, f"Could not fetch enough data. Errors: {'; '.join(errs[:3])}"
601
  prices = pd.DataFrame(data).dropna()
602
  returns = prices.pct_change().dropna()
603
  if len(returns) < 30:
604
- return None, None, "Need more aligned data."
605
  mu = returns.mean() * 252
606
  sigma = returns.cov() * 252
607
  n = len(mu)
@@ -623,92 +453,65 @@ def optimize_portfolio(tickers, period="1y"):
623
  eq_w = np.ones(n)/n
624
  eq_r = np.dot(eq_w, mu)
625
  eq_v = np.sqrt(np.dot(eq_w.T, np.dot(sigma, eq_w)))
626
- # Frontier
627
  ws = np.random.dirichlet(np.ones(n)*0.5, 3000)
628
  ws = np.clip(ws, 0, 0.4)
629
  ws = ws/ws.sum(axis=1, keepdims=True)
630
  prets = np.dot(ws, mu)
631
  pvols = np.array([np.sqrt(np.dot(w.T, np.dot(sigma, w))) for w in ws])
632
  psh = prets/(pvols+1e-10)
633
- if PLOTLY_OK:
634
- fig = go.Figure()
635
- fig.add_trace(go.Scatter(x=pvols, y=prets, mode='markers',
636
- marker=dict(size=4, color=psh, colorscale='Viridis', showscale=True,
637
- colorbar=dict(title='Sharpe')), name='Portfolios'))
638
- fig.add_trace(go.Scatter(x=[pv], y=[pr], mode='markers+text',
639
- marker=dict(size=18, color='#FF5252', symbol='star'),
640
- text=['Optimal'], textposition='top center', name='Optimal'))
641
- fig.add_trace(go.Scatter(x=[eq_v], y=[eq_r], mode='markers+text',
642
- marker=dict(size=14, color='#FF9800', symbol='diamond'),
643
- text=['Equal'], textposition='bottom center', name='Equal Weight'))
644
- fig.update_layout(title='Efficient Frontier', xaxis_title='Volatility',
645
- yaxis_title='Return', template='plotly_dark', height=550,
646
- paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
647
- else:
648
- fig = None
 
 
 
649
  wdf = pd.DataFrame({'Ticker': list(data.keys()),
650
  'Optimal (%)': np.round(best_w*100, 2),
651
  'Equal (%)': np.round(eq_w*100, 2)})
652
- # Correlation matrix
653
- corr = returns.corr()
654
- corr_fig = None
655
- if PLOTLY_OK:
656
- corr_fig = go.Figure(data=go.Heatmap(z=corr.values, x=corr.columns, y=corr.columns,
657
- colorscale='RdBu', zmid=0, text=np.round(corr.values,2), texttemplate='%{text:.2f}',
658
- colorbar=dict(title='Correlation')))
659
- corr_fig.update_layout(title='Correlation Matrix', template='plotly_dark', height=450,
660
- paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
661
- md = f"""## 📊 Portfolio Optimization Results
662
 
663
  **Tickers:** {', '.join(list(data.keys()))}
664
 
665
- ### Optimal (Max Sharpe)
666
- | Metric | Value |
667
- |--------|-------|
668
- | Expected Return | {pr*100:.1f}% |
669
- | Volatility | {pv*100:.1f}% |
670
- | Sharpe | {best_sh:.2f} |
671
 
672
- ### Equal Weight Benchmark
673
- | Metric | Value |
674
- |--------|-------|
675
- | Expected Return | {eq_r*100:.1f}% |
676
- | Volatility | {eq_v*100:.1f}% |
677
- | Sharpe | {eq_r/(eq_v+1e-10):.2f} |
678
 
679
- ### Improvements vs Equal Weight
680
- | Metric | Improvement |
681
- |--------|-------------|
682
- | Sharpe | {((best_sh/(eq_r/(eq_v+1e-10))-1)*100):+.1f}% |
683
- | Return | {((pr/eq_r-1)*100):+.1f}% |
684
- | Risk | {((1-pv/eq_v)*100):+.1f}% |
685
-
686
- ### Optimal Weights
687
  {wdf.to_markdown(index=False)}
688
  """
689
  return fig, corr_fig, wdf, md
690
 
691
- # ═══════════════════════════════════════════════════════════════
692
  # PAIRS TRADING
693
- # ═══════════════════════════════════════════════════════════════
694
  def analyze_pair(ticker_a, ticker_b, period="1y"):
695
- df_a, _ = fetch_data(ticker_a, period)
696
- df_b, _ = fetch_data(ticker_b, period)
697
  if df_a is None or df_b is None:
698
  return None, None, "Could not fetch data for one or both tickers."
699
  prices = pd.DataFrame({ticker_a: df_a['Close'], ticker_b: df_b['Close']}).dropna()
700
  if len(prices) < 30:
701
  return None, None, "Insufficient aligned data."
702
- # Spread
703
  spread = prices[ticker_a] - prices[ticker_b]
704
  spread_norm = (spread - spread.mean()) / spread.std()
705
- # Cointegration test (Engle-Granger style)
706
- from numpy import polyfit
707
- beta = polyfit(prices[ticker_b], prices[ticker_a], 1)[0]
708
  hedge_ratio = beta
709
  spread_hedged = prices[ticker_a] - hedge_ratio * prices[ticker_b]
710
  spread_hedged_norm = (spread_hedged - spread_hedged.mean()) / spread_hedged.std()
711
- # Half-life (Ornstein-Uhlenbeck)
712
  lag_spread = spread_hedged.shift(1)
713
  delta_spread = spread_hedged.diff()
714
  valid = delta_spread.dropna().index
@@ -716,44 +519,33 @@ def analyze_pair(ticker_a, ticker_b, period="1y"):
716
  x = lag_spread.loc[valid] - spread_hedged.mean()
717
  theta = -np.polyfit(x, y, 1)[0]
718
  half_life = np.log(2)/theta if theta > 0 else float('inf')
719
- # Z-score signal
720
  z = spread_hedged_norm.iloc[-1]
721
  signal = 'SHORT SPREAD' if z > 2 else 'LONG SPREAD' if z < -2 else 'NO SIGNAL'
722
- # Plot
723
- if PLOTLY_OK:
724
- fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.05,
725
- subplot_titles=(f'{ticker_a} vs {ticker_b} (Price)', 'Normalized Spread', 'Z-Score'))
726
- fig.add_trace(go.Scatter(x=prices.index, y=prices[ticker_a], line=dict(color='#2196F3', width=1.5), name=ticker_a), row=1, col=1)
727
- fig.add_trace(go.Scatter(x=prices.index, y=prices[ticker_b], line=dict(color='#FF9800', width=1.5), name=ticker_b), row=1, col=1)
728
- fig.add_trace(go.Scatter(x=prices.index, y=spread_norm, line=dict(color='#00C853', width=1.5), fill='tozeroy'), row=2, col=1)
729
- fig.add_trace(go.Scatter(x=prices.index, y=spread_hedged_norm, line=dict(color='#9C27B0', width=1.5)), row=3, col=1)
730
- fig.add_hline(y=2, line_dash="dash", line_color="#FF5252", row=3, col=1)
731
- fig.add_hline(y=-2, line_dash="dash", line_color="#00C853", row=3, col=1)
732
- fig.add_hline(y=0, line_dash="dot", line_color="gray", row=3, col=1)
733
- fig.update_layout(title=f'Pairs Trading: {ticker_a} / {ticker_b}', template='plotly_dark',
734
- height=750, paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
735
- else:
736
- fig = None
737
- # Scatter
738
- if PLOTLY_OK:
739
- scat = go.Figure()
740
- scat.add_trace(go.Scatter(x=prices[ticker_b], y=prices[ticker_a], mode='markers',
741
- marker=dict(size=4, color=np.arange(len(prices)), colorscale='Viridis', showscale=True),
742
- name='Price Path'))
743
- # Regression line
744
- x_range = np.linspace(prices[ticker_b].min(), prices[ticker_b].max(), 100)
745
- intercept = np.polyfit(prices[ticker_b], prices[ticker_a], 1)[1]
746
- y_range = hedge_ratio * x_range + intercept
747
- scat.add_trace(go.Scatter(x=x_range, y=y_range, mode='lines',
748
- line=dict(color='#FF5252', dash='dash'), name=f'OLS (β={hedge_ratio:.2f})'))
749
- scat.update_layout(title=f'Price Relationship (β={hedge_ratio:.2f})', template='plotly_dark',
750
- xaxis_title=ticker_b, yaxis_title=ticker_a, height=450,
751
- paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
752
- else:
753
- scat = None
754
- md = f"""## 🔗 Pairs Trading Analysis: {ticker_a} vs {ticker_b}
755
 
756
- ### Statistics
757
  | Metric | Value |
758
  |--------|-------|
759
  | Hedge Ratio (β) | {hedge_ratio:.3f} |
@@ -763,136 +555,124 @@ def analyze_pair(ticker_a, ticker_b, period="1y"):
763
  | Half-Life | {half_life:.1f} days |
764
 
765
  ### Signal
766
- | Current Z | Interpretation | Action |
767
- |-----------|----------------|--------|
768
- | {z:.2f} | {'Overextended' if abs(z)>2 else 'Normal range'} | **{signal}** |
769
 
770
- ### Trading Rules
771
- - **Enter Long Spread** when Z < -2 (buy {ticker_a}, short {ticker_b})
772
- - **Enter Short Spread** when Z > +2 (short {ticker_a}, buy {ticker_b})
773
  - **Exit** when Z crosses 0
774
  - **Stop Loss** when |Z| > 3.5
775
  """
776
  return fig, scat, md
777
 
778
- # ═══════════════════════════════════════════════════════════════
779
- # OPTIONS PRICING (Black-Scholes)
780
- # ═══════════════════════════════════════════════════════════════
781
  def black_scholes(S, K, T, r, sigma, option_type='call'):
782
- from math import log, sqrt, exp
783
  try:
784
- d1 = (log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*sqrt(T))
785
- d2 = d1 - sigma*sqrt(T)
786
  try:
787
  from scipy.stats import norm
788
  nd1 = norm.cdf(d1)
789
  nd2 = norm.cdf(d2)
790
  npdf_d1 = norm.pdf(d1)
791
  except:
792
- # Approximate normal CDF
793
  def approx_cdf(x):
794
  return 0.5 * (1 + math.erf(x / math.sqrt(2)))
795
  nd1 = approx_cdf(d1)
796
  nd2 = approx_cdf(d2)
797
  npdf_d1 = (1/math.sqrt(2*math.pi)) * math.exp(-0.5*d1**2)
798
  if option_type == 'call':
799
- price = S*nd1 - K*exp(-r*T)*nd2
 
800
  else:
801
- price = K*exp(-r*T)*(1-nd2) - S*(1-nd1)
802
- delta = nd1 if option_type=='call' else nd1 - 1
803
- gamma = npdf_d1 / (S*sigma*sqrt(T))
804
- theta = -(S*npdf_d1*sigma)/(2*sqrt(T)) - r*K*exp(-r*T)*nd2 if option_type=='call' else -(S*npdf_d1*sigma)/(2*sqrt(T)) + r*K*exp(-r*T)*(1-nd2)
805
- vega = S*npdf_d1*sqrt(T)
806
- rho = K*T*exp(-r*T)*nd2 if option_type=='call' else -K*T*exp(-r*T)*(1-nd2)
807
  return {'price': price, 'delta': delta, 'gamma': gamma, 'theta': theta/252,
808
  'vega': vega/100, 'rho': rho/100, 'd1': d1, 'd2': d2}
809
  except Exception as e:
810
  return {'error': str(e)}
811
 
812
  def analyze_options(ticker, strike_pct, days, rfr, vol_override, option_type):
813
- df, info = fetch_data(ticker, "6mo")
814
  if df is None:
815
- return None, None, "Could not fetch data."
816
  df = calc_indicators(df)
817
  S = df['Close'].iloc[-1]
818
  K = S * (strike_pct/100)
819
  T = days / 365
820
- # Volatility
821
- if vol_override:
822
  sigma = vol_override / 100
823
  else:
824
  sigma = df['Ret'].dropna().std() * np.sqrt(252)
825
- # Risk-free rate
826
  r = rfr / 100
827
  bs = black_scholes(S, K, T, r, sigma, option_type.lower())
828
  if 'error' in bs:
829
  return None, None, f"BS Error: {bs['error']}"
830
- # P/L table
831
  pct_changes = np.arange(-30, 31, 5)
832
  pl_data = []
833
  for pct in pct_changes:
834
  new_S = S * (1 + pct/100)
835
- new_bs = black_scholes(new_S, K, T - 1/365, r, sigma, option_type.lower())
836
- pl = (new_bs['price'] - bs['price']) * 100 # per contract
837
  pl_data.append({'Price Change %': f'{pct:+d}%', 'Stock Price': f'${new_S:.2f}',
838
  'Option Price': f'${new_bs["price"]:.2f}', 'P/L (per 100)': f'${pl:+.2f}'})
839
  pl_df = pd.DataFrame(pl_data)
840
- # Greeks chart
841
- if PLOTLY_OK:
842
- strikes = np.linspace(S*0.7, S*1.3, 50)
843
- greeks = {'price': [], 'delta': [], 'gamma': [], 'theta': [], 'vega': []}
844
- for st in strikes:
845
- res = black_scholes(S, st, T, r, sigma, option_type.lower())
846
- for k in greeks:
847
- greeks[k].append(res.get(k, 0))
848
- fig = make_subplots(rows=2, cols=3, subplot_titles=('Price', 'Delta', 'Gamma', 'Theta (daily)', 'Vega', 'P/L at Expiry'),
849
- vertical_spacing=0.12, horizontal_spacing=0.08)
850
- colors = ['#2196F3', '#00C853', '#FF9800', '#FF5252', '#9C27B0', '#673AB7']
851
- for i, (k, v) in enumerate(greeks.items()):
852
- row, col = (i//3)+1, (i%3)+1
853
- fig.add_trace(go.Scatter(x=strikes, y=v, line=dict(color=colors[i], width=2), name=k), row=row, col=col)
854
- fig.add_vline(x=S, line_dash='dash', line_color='gray', row=row, col=col)
855
- # P/L at expiry
856
- expiry_payoff = [max(s-K,0) if option_type.lower()=='call' else max(K-s,0) for s in strikes]
857
- pl_expiry = [p - bs['price'] for p in expiry_payoff]
858
- fig.add_trace(go.Scatter(x=strikes, y=pl_expiry, line=dict(color='#673AB7', width=2), name='P/L Expiry'), row=2, col=3)
859
- fig.add_hline(y=0, line_dash='dot', line_color='gray', row=2, col=3)
860
- fig.update_layout(title=f'{ticker} {option_type.title()} Greeks (S=${S:.2f}, K=${K:.2f}, T={days}d, σ={sigma*100:.1f}%)',
861
- template='plotly_dark', height=650,
862
- paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
863
- else:
864
- fig = None
865
- md = f"""## 📐 {ticker} {option_type.title()} Option Analysis
866
 
867
- ### Parameters
868
  | Parameter | Value |
869
  |-----------|-------|
870
- | Spot Price (S) | ${S:.2f} |
871
  | Strike (K) | ${K:.2f} ({strike_pct:.0f}% of spot) |
872
- | Time to Expiry (T) | {days} days ({T:.3f} years) |
873
  | Risk-Free Rate | {r*100:.2f}% |
874
- | Implied Volatility | {sigma*100:.1f}% |
875
-
876
- ### Black-Scholes Price & Greeks
877
- | Greek | Value | Interpretation |
878
- |-------|-------|----------------|
879
- | **Price** | ${bs['price']:.3f} | Option fair value |
880
- | **Delta** | {bs['delta']:.4f} | {abs(bs['delta'])*100:.1f}% hedge ratio |
881
- | **Gamma** | {bs['gamma']:.6f} | Delta sensitivity per $1 move |
882
- | **Theta** | ${bs['theta']:.4f}/day | Daily time decay |
883
- | **Vega** | ${bs['vega']:.4f} | Price change per 1% vol move |
884
- | **Rho** | ${bs['rho']:.4f} | Price change per 1% rate move |
885
- | **d1** | {bs['d1']:.4f} | Distance to exercise (std dev) |
886
- | **d2** | {bs['d2']:.4f} | Risk-neutral probability adj |
887
-
888
- ### P/L Scenario Table (per 100 contracts)
889
  {pl_df.to_markdown(index=False)}
890
  """
891
  return fig, pl_df, md
892
 
893
- # ═══════════════════════════════════════════════════════════════
894
- # MACRO ANALYSIS
895
- # ═══════════════════════════════════════════════════════════════
896
  def get_macro_data():
897
  macros = {}
898
  for t, name in [('^GSPC','S&P 500'),('^IXIC','Nasdaq'),('^TNX','10Y Treasury'),
@@ -901,59 +681,62 @@ def get_macro_data():
901
  try:
902
  df = yf.Ticker(t).history(period='1mo')
903
  if not df.empty:
904
- macros[name] = {'price': df['Close'].iloc[-1], 'change_1m': (df['Close'].iloc[-1]/df['Close'].iloc[0]-1)*100,
905
- 'high': df['High'].max(), 'low': df['Low'].min()}
906
  except:
907
  pass
908
  return macros
909
 
910
- # ═══════════════════════════════════════════════════════════════
 
 
 
 
 
 
 
911
  # UI FUNCTIONS
912
- # ═══════════════════════════════════════════════════════════════
913
  def analyze_stock(ticker, market_preset, period, interval):
914
  ticker = ticker.strip().upper()
915
  if not ticker:
916
- return [None]*7 + ["Enter a ticker."]
917
  suffix = MARKET_PRESETS.get(market_preset, {}).get('suffix', '')
918
- if suffix and not ticker.endswith(suffix.split('.')[-1]):
919
  ticker = ticker + suffix
920
- df, info = fetch_data(ticker, period)
921
  if df is None:
922
- return [None]*7 + [f"Error: {info}"]
923
  df = calc_indicators(df)
924
  sg = calc_signals(df)
925
  rk = calc_risk(df)
926
  if not rk:
927
- return [None]*7 + ["Need more data."]
928
  l = df.iloc[-1]
929
  p = df.iloc[-2] if len(df)>1 else l
930
  ch = ((l['Close']/p['Close']-1)*100) if p['Close']>0 else 0
931
- # Charts
932
  c1 = make_candlestick(df, ticker, market_preset)
933
  c2 = make_macd(df, ticker)
934
  c3 = make_stoch(df, ticker)
935
  c4 = make_vol(df, ticker)
936
  c5 = make_adx(df, ticker)
937
  c6 = make_dist(df['Ret'].dropna(), ticker)
938
- # Summary
939
- mkt_info = ""
940
  if info:
941
- mkt_info = f"""
942
- | Info | Value |
943
- |------|-------|
944
- | Name | {info.get('longName', ticker)} |
945
- | Sector | {info.get('sector', 'N/A')} |
946
- | Industry | {info.get('industry', 'N/A')} |
947
- | Market Cap | {info.get('marketCap', 'N/A'):,}" if info.get('marketCap') else ""
948
- mkt_info += f"""
949
- | 52W High | ${info.get('fiftyTwoWeekHigh', 'N/A'):.2f}" if info.get('fiftyTwoWeekHigh') else ""
950
- mkt_info += f"""
951
- | 52W Low | ${info.get('fiftyTwoWeekLow', 'N/A'):.2f}" if info.get('fiftyTwoWeekLow') else ""
952
- mkt_info += f"""
953
- | P/E | {info.get('trailingPE', 'N/A')} |""" if info.get('trailingPE') else ""
954
- md = f"""# {ticker} — {sg['dir']} {sg['strength']} (Score: {sg['score']}/100)
955
 
956
  **Price:** ${l['Close']:.2f} | **Change:** {ch:+.2f}% | **Period:** {period}
 
957
  {mkt_info}
958
 
959
  ## Signal Dashboard
@@ -969,7 +752,7 @@ def analyze_stock(ticker, market_preset, period, interval):
969
  | Trend | {sg['trend'].upper()} | — |
970
  | Momentum | {sg['mom']} | — |
971
  | Volatility | {sg['vol']} | — |
972
- | Ichimoku Cloud | {sg['ichimoku']} | — |
973
 
974
  ## Risk Metrics
975
  | Metric | Value |
@@ -993,18 +776,18 @@ def analyze_stock(ticker, market_preset, period, interval):
993
  | Kurtosis | {rk['ku']:.2f} |
994
  | 63D Rolling Sharpe | {rk['roll_sharpe']:.2f} |
995
  """
996
- return [c1, c2, c3, c4, c5, c6, md, ""]
997
 
998
  def ai_analyze_stock(ticker, market_preset, period, interval):
999
  ticker = ticker.strip().upper()
1000
  if not ticker:
1001
  return "Enter a ticker."
1002
  suffix = MARKET_PRESETS.get(market_preset, {}).get('suffix', '')
1003
- if suffix and not ticker.endswith(suffix.split('.')[-1]):
1004
  ticker = ticker + suffix
1005
- df, info = fetch_data(ticker, period)
1006
  if df is None:
1007
- return f"Error: {info}"
1008
  df = calc_indicators(df)
1009
  sg = calc_signals(df)
1010
  rk = calc_risk(df)
@@ -1037,33 +820,18 @@ def ai_portfolio(tickers, period):
1037
  client = K2ThinkClient()
1038
  return client.portfolio_advice(pd_str, corr_str, md, "Current macro: mixed signals, rates elevated, geopolitical uncertainty")
1039
 
1040
- def ai_macro():
1041
- macros = get_macro_data()
1042
- macro_text = "Global Macro Snapshot:\n"
1043
- for name, data in macros.items():
1044
- macro_text += f"- {name}: ${data['price']:.2f} (1M change: {data['change_1m']:+.1f}%)\n"
1045
- client = K2ThinkClient()
1046
- return client.macro_analysis(macro_text)
1047
-
1048
  def ai_chat(question, temp):
1049
  if not question.strip():
1050
  return "Enter a question."
1051
  client = K2ThinkClient()
1052
  return client.chat([{"role":"user","content":question}], temperature=temp, max_tokens=4096)
1053
 
1054
- # ═══════════════════════════════════════════════════════════════
1055
- # GRADIO APP — FINANCE DARK THEME
1056
- # ═══════════════════════════════════════════════════════════════
1057
  def build_app():
1058
- if gr is None:
1059
- raise ImportError(f"Gradio not available. Errors: {_import_errors}")
1060
-
1061
  with gr.Blocks(
1062
  title="AlphaForge x K2 Think V2 — Institutional Quant Platform",
1063
  theme=gr.themes.Soft(
1064
- primary_hue="blue",
1065
- secondary_hue="indigo",
1066
- neutral_hue="slate",
1067
  font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"]
1068
  ),
1069
  css="""
@@ -1073,18 +841,9 @@ def build_app():
1073
  .tab-nav { background: #0d1117 !important; border-bottom: 1px solid #30363d !important; }
1074
  .tab-nav button { color: #8b949e !important; background: transparent !important; border: none !important; }
1075
  .tab-nav button.selected { color: #58a6ff !important; border-bottom: 2px solid #58a6ff !important; }
1076
- .panel { background: #161b22 !important; border: 1px solid #30363d !important; border-radius: 12px !important; }
1077
  input, textarea, select { background: #21262d !important; color: #e6edf3 !important; border: 1px solid #30363d !important; }
1078
  button.primary { background: linear-gradient(135deg, #1f6feb, #58a6ff) !important; color: white !important; border: none !important; border-radius: 8px !important; font-weight: 600 !important; }
1079
  button.secondary { background: #21262d !important; color: #58a6ff !important; border: 1px solid #30363d !important; border-radius: 8px !important; }
1080
- .markdown-body { color: #e6edf3 !important; }
1081
- .markdown-body h1 { color: #58a6ff !important; border-bottom: 1px solid #30363d !important; }
1082
- .markdown-body h2 { color: #79c0ff !important; }
1083
- .markdown-body h3 { color: #a5d6ff !important; }
1084
- .markdown-body table { border-color: #30363d !important; }
1085
- .markdown-body th { background: #21262d !important; color: #58a6ff !important; }
1086
- .markdown-body td { border-color: #30363d !important; }
1087
- .markdown-body tr:nth-child(even) { background: #161b22 !important; }
1088
  .title-bar { text-align: center; padding: 24px 0; }
1089
  .title-bar h1 { font-size: 2.8em; font-weight: 800; margin: 0; background: linear-gradient(90deg, #58a6ff, #a371f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
1090
  .title-bar p { color: #8b949e; font-size: 1.1em; margin-top: 8px; }
@@ -1093,45 +852,34 @@ def build_app():
1093
  .badge-api { background: linear-gradient(135deg, #1f6feb, #a371f7); color: white; }
1094
  .badge-data { background: #238636; color: white; }
1095
  .badge-alpha { background: #8957e5; color: white; }
1096
- .k2-status { text-align: center; padding: 8px; margin: 8px 0; border-radius: 8px; font-size: 0.9em; }
1097
- .k2-active { background: rgba(35,134,54,0.2); color: #3fb950; border: 1px solid #238636; }
1098
- .k2-inactive { background: rgba(209,36,47,0.2); color: #f85149; border: 1px solid #da3633; }
1099
  """
1100
  ) as demo:
1101
-
1102
- # Header
1103
  gr.HTML("""
1104
  <div class="title-bar">
1105
  <h1>🔥 AlphaForge x K2 Think V2</h1>
1106
- <p>Institutional-Grade Quantitative Analysis Platform Powered by MBZUAI's State-of-the-Art Reasoning Model</p>
1107
  </div>
1108
  <div class="badge-row">
1109
  <span class="badge badge-api">🤖 K2 Think V2</span>
1110
- <span class="badge badge-data">📊 Multi-Market Data</span>
1111
- <span class="badge badge-alpha">🎯 AI Alpha Engine</span>
1112
- <span class="badge badge-data">📐 Options Pricing</span>
1113
- <span class="badge badge-alpha">🔗 Pairs Trading</span>
1114
- <span class="badge badge-api">🌍 Macro Analysis</span>
1115
  </div>
1116
  """)
1117
 
1118
- # K2 Status
1119
- k2_status_html = gr.HTML(f"""
1120
- <div class="k2-status {'k2-active' if K2_API_KEY else 'k2-inactive'}">
1121
- {'✅ K2 Think V2 API Connected' if K2_API_KEY else '⚠️ K2 Think V2 API Not Configured — Add K2_API_KEY in Space Settings > Repository Secrets'}
1122
- </div>
1123
- """)
1124
 
1125
- # ── TAB 1: SINGLE STOCK ──
1126
  with gr.Tab("📈 Technical Analysis"):
1127
  with gr.Row():
1128
  with gr.Column(scale=1):
1129
- mkt_select = gr.Dropdown(
1130
- label="🌍 Market", choices=list(MARKET_PRESETS.keys()),
1131
- value="🇺🇸 US Equities"
1132
- )
1133
  ticker_in = gr.Textbox(label="Ticker", value="AAPL", placeholder="e.g., AAPL, BTC-USD, EURUSD=X")
1134
- gr.HTML("<small style='color:#8b949e'>Examples: <span id='examples'></span></small>")
1135
  period_in = gr.Dropdown(label="Period", choices=["1mo","3mo","6mo","1y","2y","5y"], value="6mo")
1136
  interval_in = gr.Dropdown(label="Interval", choices=["1d","1wk","1mo"], value="1d")
1137
  analyze_btn = gr.Button("🔍 Analyze", variant="primary", size="lg")
@@ -1150,25 +898,18 @@ def build_app():
1150
  with gr.Row():
1151
  ai_out = gr.Textbox(label="🤖 K2 Think V2 Analysis", lines=30, max_lines=50, show_copy_button=True)
1152
 
1153
- analyze_btn.click(
1154
- fn=analyze_stock,
1155
- inputs=[ticker_in, mkt_select, period_in, interval_in],
1156
- outputs=[chart1, chart2, chart3, chart4, chart5, chart6, summary_out, gr.Textbox(visible=False)]
1157
- )
1158
- ai_btn.click(
1159
- fn=ai_analyze_stock,
1160
- inputs=[ticker_in, mkt_select, period_in, interval_in],
1161
- outputs=[ai_out]
1162
- )
1163
 
1164
- # ── TAB 2: PORTFOLIO ──
1165
  with gr.Tab("💼 Portfolio Optimizer"):
1166
  with gr.Row():
1167
  with gr.Column(scale=1):
1168
  port_in = gr.Textbox(label="Tickers (comma-separated)", value="AAPL, MSFT, GOOGL, AMZN, NVDA")
1169
  port_period = gr.Dropdown(label="Lookback", choices=["6mo","1y","2y","3y"], value="1y")
1170
- opt_btn = gr.Button("🎯 Optimize Portfolio", variant="primary", size="lg")
1171
- ai_port_btn = gr.Button("🤖 AI Portfolio Advice (K2 Think V2)", variant="secondary", size="lg")
1172
  with gr.Column(scale=2):
1173
  port_md = gr.Markdown()
1174
  with gr.Row():
@@ -1178,11 +919,9 @@ def build_app():
1178
  weights_df = gr.DataFrame(label="Optimal Weights", interactive=False)
1179
  with gr.Row():
1180
  ai_port_out = gr.Textbox(label="🤖 AI Portfolio Advice", lines=25, max_lines=40, show_copy_button=True)
1181
-
1182
  opt_btn.click(fn=optimize_portfolio, inputs=[port_in, port_period], outputs=[frontier_plot, corr_plot, weights_df, port_md])
1183
  ai_port_btn.click(fn=ai_portfolio, inputs=[port_in, port_period], outputs=[ai_port_out])
1184
 
1185
- # ── TAB 3: PAIRS TRADING ──
1186
  with gr.Tab("🔗 Pairs Trading"):
1187
  with gr.Row():
1188
  with gr.Column(scale=1):
@@ -1195,10 +934,8 @@ def build_app():
1195
  with gr.Row():
1196
  pair_chart = gr.Plot(label="Spread Analysis")
1197
  pair_scatter = gr.Plot(label="Price Relationship")
1198
-
1199
  pair_btn.click(fn=analyze_pair, inputs=[pair_a, pair_b, pair_period], outputs=[pair_chart, pair_scatter, pair_md])
1200
 
1201
- # ── TAB 4: OPTIONS ──
1202
  with gr.Tab("📐 Options Pricing"):
1203
  with gr.Row():
1204
  with gr.Column(scale=1):
@@ -1214,26 +951,19 @@ def build_app():
1214
  with gr.Row():
1215
  greeks_plot = gr.Plot(label="Greeks Analysis")
1216
  opt_pl = gr.DataFrame(label="P/L Scenarios", interactive=False)
1217
-
1218
- opt_calc_btn.click(
1219
- fn=analyze_options,
1220
- inputs=[opt_ticker, opt_strike, opt_days, opt_rfr, opt_vol, opt_type],
1221
- outputs=[greeks_plot, opt_pl, opt_md]
1222
- )
1223
 
1224
- # ── TAB 5: MACRO ──
1225
  with gr.Tab("🌍 Macro Analysis"):
1226
  with gr.Row():
1227
  macro_btn = gr.Button("🌍 Analyze Global Macro (K2 Think V2)", variant="primary", size="lg")
1228
  with gr.Row():
1229
  macro_out = gr.Textbox(label="🤖 K2 Think V2 Macro Analysis", lines=40, max_lines=60, show_copy_button=True)
1230
-
1231
  macro_btn.click(fn=ai_macro, outputs=[macro_out])
1232
 
1233
- # ── TAB 6: AI CHAT ──
1234
  with gr.Tab("💬 K2 Think V2 Chat"):
1235
  gr.Markdown("## Direct Chat with K2 Think V2")
1236
- gr.Markdown("Ask any financial question strategy, market analysis, quant interview prep, portfolio advice.")
1237
  with gr.Row():
1238
  chat_in = gr.Textbox(label="Your Question", placeholder="e.g., 'Explain gamma scalping with a real trade example'", lines=4, scale=4)
1239
  chat_temp = gr.Slider(label="Temp", minimum=0, maximum=1, value=0.4, step=0.1, scale=1)
@@ -1241,7 +971,6 @@ def build_app():
1241
  chat_out = gr.Textbox(label="🤖 Response", lines=30, max_lines=50, show_copy_button=True)
1242
  chat_btn.click(fn=ai_chat, inputs=[chat_in, chat_temp], outputs=[chat_out])
1243
 
1244
- # ── TAB 7: ABOUT ──
1245
  with gr.Tab("ℹ️ About & Setup"):
1246
  gr.Markdown(f"""
1247
  ## AlphaForge x K2 Think V2
@@ -1253,7 +982,7 @@ def build_app():
1253
  |---------|-------------|
1254
  | **📈 Technical Analysis** | 18+ indicators: RSI, MACD, Bollinger, VWAP, Stochastic, ADX, Ichimoku, ATR, MFI, OBV |
1255
  | **🌍 Multi-Market** | US, EU, UK, DE, JP, CN, IN equities + Crypto + Forex + Commodities + Indices |
1256
- | **💼 Portfolio** | Mean-variance optimization, efficient frontier, correlation matrix, Sharpe maximization |
1257
  | **🔗 Pairs Trading** | Cointegration analysis, hedge ratio, half-life, Z-score signals |
1258
  | **📐 Options Pricing** | Black-Scholes + full Greeks (Delta, Gamma, Theta, Vega, Rho), P/L scenarios |
1259
  | **🌍 Macro Analysis** | Global cross-asset regime analysis via K2 Think V2 |
@@ -1280,8 +1009,5 @@ def build_app():
1280
  return demo
1281
 
1282
  if __name__ == "__main__":
1283
- if gr is None:
1284
- print(f"FATAL: Gradio not available. {_import_errors}")
1285
- exit(1)
1286
  demo = build_app()
1287
- demo.queue().launch(server_name="0.0.0.0", server_port=7860, share=False)
 
1
+ """AlphaForge x K2 Think V2 - Institutional-Grade Quantitative Analysis Platform
2
 
3
  Multi-market support: US, EU, Asia, Crypto, Forex, Commodities
4
  Finance-themed UI with dark mode, professional color scheme
5
+ Enhanced features: Options pricing, Pairs Trading, Macro Analysis
6
  Powered by MBZUAI K2 Think V2 reasoning model
7
 
8
  API Key: set via K2_API_KEY environment variable
 
10
  import os, json, traceback, warnings, math, random
11
  warnings.filterwarnings('ignore')
12
 
13
+ # Core imports
 
14
  try:
15
  import gradio as gr
 
 
 
 
16
  import requests
 
 
 
 
17
  import yfinance as yf
 
 
 
 
18
  import pandas as pd
 
 
 
 
 
19
  import numpy as np
 
 
 
 
20
  import plotly.graph_objects as go
21
  from plotly.subplots import make_subplots
22
  PLOTLY_OK = True
23
  except ImportError as e:
24
+ raise ImportError(f"Missing required package: {e}")
 
 
 
 
25
 
 
26
  # CONFIG
 
27
  K2_API_KEY = os.environ.get("K2_API_KEY", "")
28
  K2_BASE_URL = "https://api.k2think.ai/v1/chat/completions"
29
  K2_MODEL = "MBZUAI-IFM/K2-Think-v2"
30
 
31
+ # K2 THINK V2 CLIENT
 
 
32
  class K2ThinkClient:
33
  def __init__(self):
34
  self.api_key = K2_API_KEY
 
36
  self.base_url = K2_BASE_URL
37
 
38
  def chat(self, messages, temperature=0.7, max_tokens=4096):
39
+ if not self.available:
40
+ return "⚠️ K2 Think V2 API Not Configured. Add K2_API_KEY in Space Settings > Repository Secrets. All other features work perfectly!"
 
 
 
 
 
 
 
 
 
41
 
42
  payload = {"model": K2_MODEL, "messages": messages, "temperature": temperature,
43
  "max_tokens": max_tokens, "stream": False}
 
52
  return j['choices'][0]['message']['content']
53
  return f"⚠️ Unexpected format: {json.dumps(j, indent=2)[:400]}"
54
  except requests.exceptions.Timeout:
55
+ return "⏱️ Timeout after 120s. API may be under high load."
56
  except requests.exceptions.HTTPError as e:
57
  if e.response.status_code == 401:
58
+ return "🔐 Auth failed. Check K2_API_KEY secret."
59
  elif e.response.status_code == 429:
60
  return "🚦 Rate limited. Wait a moment."
61
  return f"🔴 HTTP {e.response.status_code}: {str(e)[:200]}"
 
63
  return f"🔴 Error: {str(e)[:300]}"
64
 
65
  def analyze_market(self, ticker, market, data_summary, tech_summary, timeframe):
66
+ prompt = f"""You are an elite quantitative analyst at a top hedge fund (Two Sigma / Jane Street level).
67
+ Analyze with deep chain-of-thought reasoning.
68
 
69
  ## Asset Information
70
  - **Ticker**: {ticker}
 
81
  Provide exactly these sections:
82
 
83
  ### 1. Executive Summary (3 bullets)
 
 
 
 
84
  ### 2. Technical Analysis
85
+ - Interpret RSI, MACD, Bollinger Bands, ADX, Ichimoku
86
  - Identify support/resistance levels from SMAs and VWAP
 
 
87
  ### 3. Risk Assessment
88
  - Volatility regime (low/normal/high)
89
+ - Tail risk estimate
90
+ - Correlation risk
 
91
  ### 4. Alpha Signal
92
  - Direction: BULLISH / NEUTRAL / BEARISH
93
  - Confidence: X%
94
+ - Time horizon
95
  - Key conviction drivers
 
96
  ### 5. Trade Recommendation
97
  - Entry price / zone
98
+ - Stop-loss
99
+ - Target 1 (conservative) and Target 2 (aggressive)
100
+ - Position sizing suggestion
 
 
101
  ### 6. Catalyst Calendar
102
+ - Next 7 days
103
+ - Next 30 days
 
104
  ### 7. Contrarian View
105
  - What would make this signal wrong?
106
  - Alternative scenario with probability
107
 
108
+ Think step-by-step. Reference specific numbers."""
109
  return self.chat([{"role": "user", "content": prompt}], temperature=0.2, max_tokens=4096)
110
 
111
  def portfolio_advice(self, portfolio_data, corr_data, risk_metrics, market_context):
 
125
 
126
  ## Deliverables
127
 
128
+ ### 1. Portfolio Health Score (0-100) with letter grade (A+ to F)
 
 
129
  ### 2. Concentration Risk
 
 
 
 
130
  ### 3. Correlation Risk Matrix
 
 
 
 
131
  ### 4. Rebalancing Roadmap
132
  - Specific weight adjustments with %
133
  - Timeline: immediate / 1 week / 1 month
 
 
134
  ### 5. Hedging Strategy
 
 
 
 
135
  ### 6. Expected Return & Risk (Forward 12M)
 
 
 
 
 
 
 
 
136
  ### 7. Scenario Analysis
137
+ - Bull case (20% probability)
138
  - Base case (50% probability)
139
  - Bear case (20% probability)
140
  - Tail case (10% probability)
 
151
  ## Deliverables
152
 
153
  ### 1. Macro Regime Classification
 
 
 
 
154
  ### 2. Cross-Asset Implications
155
+ - Equities, Fixed Income, FX, Commodities, Crypto
 
 
 
 
 
156
  ### 3. Trade Ideas (3 concrete setups)
157
  Each with: instrument, direction, entry, stop, target, conviction %, time horizon
 
158
  ### 4. Risk Factors
 
 
 
159
 
160
  Think like a macro PM."""
161
  return self.chat([{"role": "user", "content": prompt}], temperature=0.3, max_tokens=4096)
 
 
 
 
 
 
 
 
 
 
 
162
 
163
+ # MARKET DATA
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  MARKET_PRESETS = {
165
  "🇺🇸 US Equities": {"suffix": "", "examples": "AAPL, TSLA, NVDA, SPY, QQQ, META, AMZN, GOOGL"},
166
+ "🇪🇺 European Equities": {"suffix": ".PA", "examples": "AIR.PA, SAN.PA, TTE.PA, OR.PA, MC.PA"},
167
+ "🇬🇧 UK Equities": {"suffix": ".L", "examples": "AZN.L, SHEL.L, BP.L, ULVR.L, RIO.L"},
168
+ "🇩🇪 German Equities": {"suffix": ".DE", "examples": "SAP.DE, SIE.DE, ALV.DE, BAS.DE, BMW.DE"},
169
+ "🇯🇵 Japanese Equities": {"suffix": ".T", "examples": "7203.T, 9984.T, 6861.T, 6758.T"},
170
  "🇨🇳 Chinese Equities": {"suffix": ".HK", "examples": "0700.HK, 9988.HK, 3690.HK, 1810.HK"},
171
+ "🇮🇳 Indian Equities": {"suffix": ".NS", "examples": "RELIANCE.NS, TCS.NS, INFY.NS"},
172
+ "🪙 Crypto": {"suffix": "", "examples": "BTC-USD, ETH-USD, SOL-USD, XRP-USD"},
173
+ "💱 Forex Majors": {"suffix": "=X", "examples": "EURUSD=X, GBPUSD=X, USDJPY=X"},
174
+ "🥇 Commodities": {"suffix": "", "examples": "GC=F, SI=F, CL=F, NG=F, ZC=F"},
175
+ "📊 Indices": {"suffix": "", "examples": "^GSPC, ^DJI, ^IXIC, ^FTSE, ^N225"},
176
  }
177
 
178
  def fetch_data(ticker, period="6mo", interval="1d"):
 
 
179
  try:
180
  stock = yf.Ticker(ticker.upper().strip())
181
  df = stock.history(period=period, interval=interval)
182
  if df.empty:
183
+ return None, None, f"No data for '{ticker}'. Try examples from the selected market."
184
  info = stock.info
185
+ return df, info, None
186
  except Exception as e:
187
+ return None, None, f"Error fetching '{ticker}': {str(e)[:200]}"
188
 
189
  def calc_indicators(df):
190
  df = df.copy()
 
223
  df['Stoch_D'] = df['Stoch_K'].rolling(3).mean()
224
  df['VM'] = df['Volume'].rolling(20).mean()
225
  df['VR'] = df['Volume']/(df['VM']+1e-10)
 
226
  plus_dm = df['High'].diff()
227
  minus_dm = df['Low'].diff()
228
  plus_dm[plus_dm<0] = 0
 
233
  df['minus_DI'] = 100 * (minus_dm.ewm(alpha=1/14, adjust=False).mean() / atr_smooth)
234
  dx = 100 * np.abs(df['plus_DI']-df['minus_DI'])/(df['plus_DI']+df['minus_DI']+1e-10)
235
  df['ADX'] = dx.ewm(alpha=1/14, adjust=False).mean()
 
236
  df['OBV'] = (np.sign(df['Close'].diff())*df['Volume']).cumsum()
 
237
  tp_r = (df['High']+df['Low']+df['Close'])/3
238
  tp_diff = tp_r.diff()
239
  pos_flow = tp_r.where(tp_diff>0,0)*df['Volume']
 
241
  mfi_pos = pos_flow.rolling(14).sum()
242
  mfi_neg = neg_flow.rolling(14).sum()
243
  df['MFI'] = 100 - (100/(1+mfi_pos/(mfi_neg+1e-10)))
 
244
  df['ICH_tenkan'] = (df['High'].rolling(9).max()+df['Low'].rolling(9).min())/2
245
  df['ICH_kijun'] = (df['High'].rolling(26).max()+df['Low'].rolling(26).min())/2
246
  df['ICH_senkou_A'] = ((df['ICH_tenkan']+df['ICH_kijun'])/2).shift(26)
 
265
  cv95 = r[r<=v95].mean() if len(r[r<=v95])>0 else v95
266
  cv99 = r[r<=v99].mean() if len(r[r<=v99])>0 else v99
267
  ca = ar/(abs(md)+1e-10)
 
268
  roll_sharpe = (r.rolling(63).mean()*252)/(r.rolling(63).std()*np.sqrt(252)+1e-10)
269
  return {'ar':ar,'av':av,'sh':sh,'so':so,'md':md,'v95':v95,'v99':v99,
270
  'cv95':cv95,'cv99':cv99,'ca':ca,'sk':r.skew(),'ku':r.kurtosis(),
271
  'wr':(r>0).mean(),'pf':abs(r[r>0].sum()/(r[r<0].sum()+1e-10)),
272
  'avg_win':r[r>0].mean() if len(r[r>0])>0 else 0,
273
  'avg_loss':r[r<0].mean() if len(r[r<0])>0 else 0,
 
274
  'roll_sharpe':roll_sharpe.iloc[-1] if len(roll_sharpe.dropna())>0 else 0,
275
  'vol_regime':'low' if av<0.15 else 'normal' if av<0.30 else 'high'}
276
 
 
311
  s['adx_trend'] = 'strong trend'
312
  elif l['ADX'] > 20:
313
  s['adx_trend'] = 'trending'
 
314
  if l['Close'] > l['ICH_senkou_A'] and l['Close'] > l['ICH_senkou_B']:
315
  s['ichimoku'] = 'bullish cloud'
316
  elif l['Close'] < l['ICH_senkou_A'] and l['Close'] < l['ICH_senkou_B']:
317
  s['ichimoku'] = 'bearish cloud'
 
318
  sc = 50
319
  if 'bullish' in s['trend']: sc += 20
320
  if 'bearish' in s['trend']: sc -= 20
 
334
  return s
335
 
336
  def make_candlestick(df, ticker, market):
 
 
337
  fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.03,
338
  row_heights=[0.55, 0.25, 0.20],
339
  subplot_titles=(f'{ticker} ({market})', 'Volume + VWAP', 'RSI'))
 
340
  fig.add_trace(go.Candlestick(x=df.index, open=df['Open'], high=df['High'],
341
  low=df['Low'], close=df['Close'], name='Price',
342
  increasing_line_color='#00C853', decreasing_line_color='#FF5252'), row=1, col=1)
 
343
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA20'], line=dict(color='#FF9800', width=1), name='SMA20'), row=1, col=1)
344
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA50'], line=dict(color='#2196F3', width=1), name='SMA50'), row=1, col=1)
345
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA200'], line=dict(color='#9C27B0', width=1.5, dash='dash'), name='SMA200'), row=1, col=1)
 
346
  fig.add_trace(go.Scatter(x=df.index, y=df['BBU'], line=dict(color='gray', width=0.8, dash='dash'), name='BB+', opacity=0.4), row=1, col=1)
347
  fig.add_trace(go.Scatter(x=df.index, y=df['BBL'], line=dict(color='gray', width=0.8, dash='dash'), name='BB-', opacity=0.4), row=1, col=1)
 
 
 
 
348
  colors = ['#00C853' if df['Close'].iloc[i]>=df['Open'].iloc[i] else '#FF5252' for i in range(len(df))]
349
  fig.add_trace(go.Bar(x=df.index, y=df['Volume'], marker_color=colors, name='Volume', opacity=0.7), row=2, col=1)
350
  fig.add_trace(go.Scatter(x=df.index, y=df['VM'], line=dict(color='#FF9800', width=1), name='Vol MA20'), row=2, col=1)
 
351
  fig.add_trace(go.Scatter(x=df.index, y=df['RSI'], line=dict(color='#9C27B0', width=1.5), fill='tozeroy', fillcolor='rgba(156,39,176,0.1)'), row=3, col=1)
352
  fig.add_hline(y=70, line_dash="dash", line_color="#FF5252", row=3, col=1)
353
  fig.add_hline(y=30, line_dash="dash", line_color="#00C853", row=3, col=1)
 
362
  return fig
363
 
364
  def make_macd(df, ticker):
 
365
  fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05,
366
  row_heights=[0.6, 0.4], subplot_titles=('MACD', 'Histogram'))
367
  fig.add_trace(go.Scatter(x=df.index, y=df['MACD'], line=dict(color='#2196F3', width=1.5), name='MACD'), row=1, col=1)
 
373
  return fig
374
 
375
  def make_stoch(df, ticker):
 
376
  fig = go.Figure()
377
  fig.add_trace(go.Scatter(x=df.index, y=df['Stoch_K'], line=dict(color='#2196F3', width=1.5), name='%K'))
378
  fig.add_trace(go.Scatter(x=df.index, y=df['Stoch_D'], line=dict(color='#FF9800', width=1.5), name='%D'))
379
  fig.add_hline(y=80, line_dash="dash", line_color="#FF5252")
380
  fig.add_hline(y=20, line_dash="dash", line_color="#00C853")
381
+ fig.update_layout(title=f'{ticker} Stochastic', template='plotly_dark', height=400,
382
+ paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
 
383
  return fig
384
 
385
  def make_vol(df, ticker):
 
386
  fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05,
387
  row_heights=[0.6, 0.4], subplot_titles=('ATR %', 'Volume Ratio'))
388
  fig.add_trace(go.Scatter(x=df.index, y=df['ATR_pct'], line=dict(color='#FF9800', width=1.5), fill='tozeroy'), row=1, col=1)
389
  fig.add_trace(go.Scatter(x=df.index, y=df['VR'], line=dict(color='#9C27B0', width=1.5)), row=2, col=1)
390
  fig.add_hline(y=1.0, line_dash="dash", line_color="gray", row=2, col=1)
391
+ fig.update_layout(title=f'{ticker} Volatility', template='plotly_dark', height=500,
392
+ paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
 
393
  return fig
394
 
395
  def make_adx(df, ticker):
 
396
  fig = go.Figure()
397
  fig.add_trace(go.Scatter(x=df.index, y=df['plus_DI'], line=dict(color='#00C853', width=1), name='+DI'))
398
  fig.add_trace(go.Scatter(x=df.index, y=df['minus_DI'], line=dict(color='#FF5252', width=1), name='-DI'))
399
  fig.add_trace(go.Scatter(x=df.index, y=df['ADX'], line=dict(color='#2196F3', width=2), name='ADX'))
400
  fig.add_hline(y=25, line_dash="dash", line_color="gray")
401
+ fig.update_layout(title=f'{ticker} ADX', template='plotly_dark', height=400,
402
+ paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
 
403
  return fig
404
 
405
  def make_dist(r, ticker):
 
406
  fig = go.Figure()
407
  fig.add_trace(go.Histogram(x=r, nbinsx=50, marker_color='#2196F3', opacity=0.7, name='Returns'))
408
  mu, sig = r.mean(), r.std()
 
 
 
 
 
 
 
 
409
  fig.add_vline(x=mu, line_color='#00C853', line_dash='dash', annotation_text=f'Mean: {mu*100:.2f}%')
410
+ fig.add_vline(x=np.percentile(r,5), line_color='#FF5252', line_dash='dot', annotation_text='VaR95')
411
+ fig.update_layout(title=f'{ticker} Returns', xaxis_title='Daily Return', yaxis_title='Count',
412
+ height=400, template='plotly_dark',
413
  paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
414
  return fig
415
 
416
+ # PORTFOLIO
 
 
417
  def optimize_portfolio(tickers, period="1y"):
 
 
418
  ts = [t.strip().upper() for t in tickers.split(',') if t.strip()]
419
  if len(ts) < 2:
420
+ return None, None, None, "Enter at least 2 tickers."
421
  data = {}
422
  errs = []
423
  for t in ts:
424
+ df, info, err = fetch_data(t, period)
425
  if err:
426
  errs.append(err)
427
  elif df is not None and len(df) > 30:
428
  data[t] = df['Close']
429
  if len(data) < 2:
430
+ return None, None, None, f"Could not fetch data: {'; '.join(errs[:3])}"
431
  prices = pd.DataFrame(data).dropna()
432
  returns = prices.pct_change().dropna()
433
  if len(returns) < 30:
434
+ return None, None, None, "Need more data."
435
  mu = returns.mean() * 252
436
  sigma = returns.cov() * 252
437
  n = len(mu)
 
453
  eq_w = np.ones(n)/n
454
  eq_r = np.dot(eq_w, mu)
455
  eq_v = np.sqrt(np.dot(eq_w.T, np.dot(sigma, eq_w)))
 
456
  ws = np.random.dirichlet(np.ones(n)*0.5, 3000)
457
  ws = np.clip(ws, 0, 0.4)
458
  ws = ws/ws.sum(axis=1, keepdims=True)
459
  prets = np.dot(ws, mu)
460
  pvols = np.array([np.sqrt(np.dot(w.T, np.dot(sigma, w))) for w in ws])
461
  psh = prets/(pvols+1e-10)
462
+ fig = go.Figure()
463
+ fig.add_trace(go.Scatter(x=pvols, y=prets, mode='markers',
464
+ marker=dict(size=4, color=psh, colorscale='Viridis', showscale=True,
465
+ colorbar=dict(title='Sharpe')), name='Portfolios'))
466
+ fig.add_trace(go.Scatter(x=[pv], y=[pr], mode='markers+text',
467
+ marker=dict(size=18, color='#FF5252', symbol='star'),
468
+ text=['Optimal'], textposition='top center', name='Optimal'))
469
+ fig.add_trace(go.Scatter(x=[eq_v], y=[eq_r], mode='markers+text',
470
+ marker=dict(size=14, color='#FF9800', symbol='diamond'),
471
+ text=['Equal'], textposition='bottom center', name='Equal Weight'))
472
+ fig.update_layout(title='Efficient Frontier', xaxis_title='Volatility', yaxis_title='Return',
473
+ template='plotly_dark', height=550,
474
+ paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
475
+ corr = returns.corr()
476
+ corr_fig = go.Figure(data=go.Heatmap(z=corr.values, x=corr.columns, y=corr.columns,
477
+ colorscale='RdBu', zmid=0, text=np.round(corr.values,2), texttemplate='%{text:.2f}',
478
+ colorbar=dict(title='Correlation')))
479
+ corr_fig.update_layout(title='Correlation Matrix', template='plotly_dark', height=450,
480
+ paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
481
  wdf = pd.DataFrame({'Ticker': list(data.keys()),
482
  'Optimal (%)': np.round(best_w*100, 2),
483
  'Equal (%)': np.round(eq_w*100, 2)})
484
+ md = f"""## Portfolio Results
 
 
 
 
 
 
 
 
 
485
 
486
  **Tickers:** {', '.join(list(data.keys()))}
487
 
488
+ | | Optimal | Equal |
489
+ |-|---------|-------|
490
+ | Return | {pr*100:.1f}% | {eq_r*100:.1f}% |
491
+ | Volatility | {pv*100:.1f}% | {eq_v*100:.1f}% |
492
+ | Sharpe | {best_sh:.2f} | {eq_r/(eq_v+1e-10):.2f} |
 
493
 
494
+ Improvements: Sharpe {((best_sh/(eq_r/(eq_v+1e-10))-1)*100):+.1f}%
 
 
 
 
 
495
 
 
 
 
 
 
 
 
 
496
  {wdf.to_markdown(index=False)}
497
  """
498
  return fig, corr_fig, wdf, md
499
 
 
500
  # PAIRS TRADING
 
501
  def analyze_pair(ticker_a, ticker_b, period="1y"):
502
+ df_a, _, _ = fetch_data(ticker_a, period)
503
+ df_b, _, _ = fetch_data(ticker_b, period)
504
  if df_a is None or df_b is None:
505
  return None, None, "Could not fetch data for one or both tickers."
506
  prices = pd.DataFrame({ticker_a: df_a['Close'], ticker_b: df_b['Close']}).dropna()
507
  if len(prices) < 30:
508
  return None, None, "Insufficient aligned data."
 
509
  spread = prices[ticker_a] - prices[ticker_b]
510
  spread_norm = (spread - spread.mean()) / spread.std()
511
+ beta = np.polyfit(prices[ticker_b], prices[ticker_a], 1)[0]
 
 
512
  hedge_ratio = beta
513
  spread_hedged = prices[ticker_a] - hedge_ratio * prices[ticker_b]
514
  spread_hedged_norm = (spread_hedged - spread_hedged.mean()) / spread_hedged.std()
 
515
  lag_spread = spread_hedged.shift(1)
516
  delta_spread = spread_hedged.diff()
517
  valid = delta_spread.dropna().index
 
519
  x = lag_spread.loc[valid] - spread_hedged.mean()
520
  theta = -np.polyfit(x, y, 1)[0]
521
  half_life = np.log(2)/theta if theta > 0 else float('inf')
 
522
  z = spread_hedged_norm.iloc[-1]
523
  signal = 'SHORT SPREAD' if z > 2 else 'LONG SPREAD' if z < -2 else 'NO SIGNAL'
524
+ fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.05,
525
+ subplot_titles=(f'{ticker_a} vs {ticker_b}', 'Normalized Spread', 'Z-Score'))
526
+ fig.add_trace(go.Scatter(x=prices.index, y=prices[ticker_a], line=dict(color='#2196F3', width=1.5), name=ticker_a), row=1, col=1)
527
+ fig.add_trace(go.Scatter(x=prices.index, y=prices[ticker_b], line=dict(color='#FF9800', width=1.5), name=ticker_b), row=1, col=1)
528
+ fig.add_trace(go.Scatter(x=prices.index, y=spread_norm, line=dict(color='#00C853', width=1.5), fill='tozeroy'), row=2, col=1)
529
+ fig.add_trace(go.Scatter(x=prices.index, y=spread_hedged_norm, line=dict(color='#9C27B0', width=1.5)), row=3, col=1)
530
+ fig.add_hline(y=2, line_dash="dash", line_color="#FF5252", row=3, col=1)
531
+ fig.add_hline(y=-2, line_dash="dash", line_color="#00C853", row=3, col=1)
532
+ fig.add_hline(y=0, line_dash="dot", line_color="gray", row=3, col=1)
533
+ fig.update_layout(title=f'Pairs: {ticker_a} / {ticker_b}', template='plotly_dark',
534
+ height=750, paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
535
+ scat = go.Figure()
536
+ scat.add_trace(go.Scatter(x=prices[ticker_b], y=prices[ticker_a], mode='markers',
537
+ marker=dict(size=4, color=np.arange(len(prices)), colorscale='Viridis', showscale=True),
538
+ name='Price Path'))
539
+ x_range = np.linspace(prices[ticker_b].min(), prices[ticker_b].max(), 100)
540
+ intercept = np.polyfit(prices[ticker_b], prices[ticker_a], 1)[1]
541
+ y_range = hedge_ratio * x_range + intercept
542
+ scat.add_trace(go.Scatter(x=x_range, y=y_range, mode='lines',
543
+ line=dict(color='#FF5252', dash='dash'), name=f'OLS ={hedge_ratio:.2f})'))
544
+ scat.update_layout(title=f'Price Relationship (β={hedge_ratio:.2f})', template='plotly_dark',
545
+ xaxis_title=ticker_b, yaxis_title=ticker_a, height=450,
546
+ paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
547
+ md = f"""## Pairs Trading: {ticker_a} vs {ticker_b}
 
 
 
 
 
 
 
 
 
548
 
 
549
  | Metric | Value |
550
  |--------|-------|
551
  | Hedge Ratio (β) | {hedge_ratio:.3f} |
 
555
  | Half-Life | {half_life:.1f} days |
556
 
557
  ### Signal
558
+ | Z-Score | Action |
559
+ |---------|--------|
560
+ | {z:.2f} | **{signal}** |
561
 
562
+ ### Rules
563
+ - **Long Spread** when Z < -2 (buy {ticker_a}, short {ticker_b})
564
+ - **Short Spread** when Z > +2 (short {ticker_a}, buy {ticker_b})
565
  - **Exit** when Z crosses 0
566
  - **Stop Loss** when |Z| > 3.5
567
  """
568
  return fig, scat, md
569
 
570
+ # OPTIONS
 
 
571
  def black_scholes(S, K, T, r, sigma, option_type='call'):
 
572
  try:
573
+ d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
574
+ d2 = d1 - sigma*np.sqrt(T)
575
  try:
576
  from scipy.stats import norm
577
  nd1 = norm.cdf(d1)
578
  nd2 = norm.cdf(d2)
579
  npdf_d1 = norm.pdf(d1)
580
  except:
 
581
  def approx_cdf(x):
582
  return 0.5 * (1 + math.erf(x / math.sqrt(2)))
583
  nd1 = approx_cdf(d1)
584
  nd2 = approx_cdf(d2)
585
  npdf_d1 = (1/math.sqrt(2*math.pi)) * math.exp(-0.5*d1**2)
586
  if option_type == 'call':
587
+ price = S*nd1 - K*math.exp(-r*T)*nd2
588
+ delta = nd1
589
  else:
590
+ price = K*math.exp(-r*T)*(1-nd2) - S*(1-nd1)
591
+ delta = nd1 - 1
592
+ gamma = npdf_d1 / (S*sigma*np.sqrt(T))
593
+ theta = -(S*npdf_d1*sigma)/(2*np.sqrt(T)) - r*K*math.exp(-r*T)*nd2 if option_type=='call' else -(S*npdf_d1*sigma)/(2*np.sqrt(T)) + r*K*math.exp(-r*T)*(1-nd2)
594
+ vega = S*npdf_d1*np.sqrt(T)
595
+ rho = K*T*math.exp(-r*T)*nd2 if option_type=='call' else -K*T*math.exp(-r*T)*(1-nd2)
596
  return {'price': price, 'delta': delta, 'gamma': gamma, 'theta': theta/252,
597
  'vega': vega/100, 'rho': rho/100, 'd1': d1, 'd2': d2}
598
  except Exception as e:
599
  return {'error': str(e)}
600
 
601
  def analyze_options(ticker, strike_pct, days, rfr, vol_override, option_type):
602
+ df, info, err = fetch_data(ticker, "6mo")
603
  if df is None:
604
+ return None, None, f"Error: {err}"
605
  df = calc_indicators(df)
606
  S = df['Close'].iloc[-1]
607
  K = S * (strike_pct/100)
608
  T = days / 365
609
+ if vol_override and vol_override > 0:
 
610
  sigma = vol_override / 100
611
  else:
612
  sigma = df['Ret'].dropna().std() * np.sqrt(252)
 
613
  r = rfr / 100
614
  bs = black_scholes(S, K, T, r, sigma, option_type.lower())
615
  if 'error' in bs:
616
  return None, None, f"BS Error: {bs['error']}"
 
617
  pct_changes = np.arange(-30, 31, 5)
618
  pl_data = []
619
  for pct in pct_changes:
620
  new_S = S * (1 + pct/100)
621
+ new_bs = black_scholes(new_S, K, max(T - 1/365, 0.001), r, sigma, option_type.lower())
622
+ pl = (new_bs['price'] - bs['price']) * 100
623
  pl_data.append({'Price Change %': f'{pct:+d}%', 'Stock Price': f'${new_S:.2f}',
624
  'Option Price': f'${new_bs["price"]:.2f}', 'P/L (per 100)': f'${pl:+.2f}'})
625
  pl_df = pd.DataFrame(pl_data)
626
+ strikes = np.linspace(S*0.7, S*1.3, 50)
627
+ greeks_data = {'price': [], 'delta': [], 'gamma': [], 'theta': [], 'vega': []}
628
+ for st in strikes:
629
+ res = black_scholes(S, st, T, r, sigma, option_type.lower())
630
+ for k in greeks_data:
631
+ greeks_data[k].append(res.get(k, 0))
632
+ fig = make_subplots(rows=2, cols=3,
633
+ subplot_titles=('Price', 'Delta', 'Gamma', 'Theta (daily)', 'Vega', 'P/L at Expiry'),
634
+ vertical_spacing=0.12, horizontal_spacing=0.08)
635
+ colors = ['#2196F3', '#00C853', '#FF9800', '#FF5252', '#9C27B0', '#673AB7']
636
+ for i, (k, v) in enumerate(greeks_data.items()):
637
+ row, col = (i//3)+1, (i%3)+1
638
+ fig.add_trace(go.Scatter(x=strikes, y=v, line=dict(color=colors[i], width=2), name=k), row=row, col=col)
639
+ fig.add_vline(x=S, line_dash='dash', line_color='gray', row=row, col=col)
640
+ expiry_payoff = [max(s-K,0) if option_type.lower()=='call' else max(K-s,0) for s in strikes]
641
+ pl_expiry = [p - bs['price'] for p in expiry_payoff]
642
+ fig.add_trace(go.Scatter(x=strikes, y=pl_expiry, line=dict(color='#673AB7', width=2), name='P/L Expiry'), row=2, col=3)
643
+ fig.add_hline(y=0, line_dash='dot', line_color='gray', row=2, col=3)
644
+ fig.update_layout(
645
+ title=f'{ticker} {option_type.title()} Greeks (S=${S:.2f}, K=${K:.2f}, T={days}d, σ={sigma*100:.1f}%)',
646
+ template='plotly_dark', height=650,
647
+ paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3'))
648
+ md = f"""## {ticker} {option_type.title()} Analysis
 
 
 
649
 
 
650
  | Parameter | Value |
651
  |-----------|-------|
652
+ | Spot (S) | ${S:.2f} |
653
  | Strike (K) | ${K:.2f} ({strike_pct:.0f}% of spot) |
654
+ | Time to Expiry | {days} days |
655
  | Risk-Free Rate | {r*100:.2f}% |
656
+ | Volatility | {sigma*100:.1f}% |
657
+
658
+ ### Greeks
659
+ | Greek | Value |
660
+ |-------|-------|
661
+ | **Price** | ${bs['price']:.3f} |
662
+ | **Delta** | {bs['delta']:.4f} |
663
+ | **Gamma** | {bs['gamma']:.6f} |
664
+ | **Theta** | ${bs['theta']:.4f}/day |
665
+ | **Vega** | ${bs['vega']:.4f} |
666
+ | **Rho** | ${bs['rho']:.4f} |
667
+ | **d1** | {bs['d1']:.4f} |
668
+ | **d2** | {bs['d2']:.4f} |
669
+
670
+ ### P/L Scenarios (per 100 contracts)
671
  {pl_df.to_markdown(index=False)}
672
  """
673
  return fig, pl_df, md
674
 
675
+ # MACRO
 
 
676
  def get_macro_data():
677
  macros = {}
678
  for t, name in [('^GSPC','S&P 500'),('^IXIC','Nasdaq'),('^TNX','10Y Treasury'),
 
681
  try:
682
  df = yf.Ticker(t).history(period='1mo')
683
  if not df.empty:
684
+ macros[name] = {'price': df['Close'].iloc[-1], 'change_1m': (df['Close'].iloc[-1]/df['Close'].iloc[0]-1)*100}
 
685
  except:
686
  pass
687
  return macros
688
 
689
+ def ai_macro():
690
+ macros = get_macro_data()
691
+ macro_text = "Global Macro Snapshot:\n"
692
+ for name, data in macros.items():
693
+ macro_text += f"- {name}: ${data['price']:.2f} (1M change: {data['change_1m']:+.1f}%)\n"
694
+ client = K2ThinkClient()
695
+ return client.macro_analysis(macro_text)
696
+
697
  # UI FUNCTIONS
 
698
  def analyze_stock(ticker, market_preset, period, interval):
699
  ticker = ticker.strip().upper()
700
  if not ticker:
701
+ return [None]*6 + ["Enter a ticker."]
702
  suffix = MARKET_PRESETS.get(market_preset, {}).get('suffix', '')
703
+ if suffix and not any(ticker.endswith(s) for s in suffix.split('|')):
704
  ticker = ticker + suffix
705
+ df, info, err = fetch_data(ticker, period)
706
  if df is None:
707
+ return [None]*6 + [f"Error: {err}"]
708
  df = calc_indicators(df)
709
  sg = calc_signals(df)
710
  rk = calc_risk(df)
711
  if not rk:
712
+ return [None]*6 + ["Need more data."]
713
  l = df.iloc[-1]
714
  p = df.iloc[-2] if len(df)>1 else l
715
  ch = ((l['Close']/p['Close']-1)*100) if p['Close']>0 else 0
 
716
  c1 = make_candlestick(df, ticker, market_preset)
717
  c2 = make_macd(df, ticker)
718
  c3 = make_stoch(df, ticker)
719
  c4 = make_vol(df, ticker)
720
  c5 = make_adx(df, ticker)
721
  c6 = make_dist(df['Ret'].dropna(), ticker)
722
+ info_lines = []
 
723
  if info:
724
+ info_lines.append(f"| Name | {info.get('longName', ticker)} |")
725
+ info_lines.append(f"| Sector | {info.get('sector', 'N/A')} |")
726
+ info_lines.append(f"| Industry | {info.get('industry', 'N/A')} |")
727
+ if info.get('marketCap'):
728
+ info_lines.append(f"| Market Cap | {info.get('marketCap'):,} |")
729
+ if info.get('fiftyTwoWeekHigh'):
730
+ info_lines.append(f"| 52W High | ${info.get('fiftyTwoWeekHigh'):.2f} |")
731
+ if info.get('fiftyTwoWeekLow'):
732
+ info_lines.append(f"| 52W Low | ${info.get('fiftyTwoWeekLow'):.2f} |")
733
+ if info.get('trailingPE'):
734
+ info_lines.append(f"| P/E | {info.get('trailingPE'):.2f} |")
735
+ mkt_info = "\n".join(info_lines)
736
+ md = f"""# {ticker} - {sg['dir']} {sg['strength']} (Score: {sg['score']}/100)
 
737
 
738
  **Price:** ${l['Close']:.2f} | **Change:** {ch:+.2f}% | **Period:** {period}
739
+
740
  {mkt_info}
741
 
742
  ## Signal Dashboard
 
752
  | Trend | {sg['trend'].upper()} | — |
753
  | Momentum | {sg['mom']} | — |
754
  | Volatility | {sg['vol']} | — |
755
+ | Ichimoku | {sg['ichimoku']} | — |
756
 
757
  ## Risk Metrics
758
  | Metric | Value |
 
776
  | Kurtosis | {rk['ku']:.2f} |
777
  | 63D Rolling Sharpe | {rk['roll_sharpe']:.2f} |
778
  """
779
+ return [c1, c2, c3, c4, c5, c6, md]
780
 
781
  def ai_analyze_stock(ticker, market_preset, period, interval):
782
  ticker = ticker.strip().upper()
783
  if not ticker:
784
  return "Enter a ticker."
785
  suffix = MARKET_PRESETS.get(market_preset, {}).get('suffix', '')
786
+ if suffix and not any(ticker.endswith(s) for s in suffix.split('|')):
787
  ticker = ticker + suffix
788
+ df, info, err = fetch_data(ticker, period)
789
  if df is None:
790
+ return f"Error: {err}"
791
  df = calc_indicators(df)
792
  sg = calc_signals(df)
793
  rk = calc_risk(df)
 
820
  client = K2ThinkClient()
821
  return client.portfolio_advice(pd_str, corr_str, md, "Current macro: mixed signals, rates elevated, geopolitical uncertainty")
822
 
 
 
 
 
 
 
 
 
823
  def ai_chat(question, temp):
824
  if not question.strip():
825
  return "Enter a question."
826
  client = K2ThinkClient()
827
  return client.chat([{"role":"user","content":question}], temperature=temp, max_tokens=4096)
828
 
829
+ # GRADIO APP
 
 
830
  def build_app():
 
 
 
831
  with gr.Blocks(
832
  title="AlphaForge x K2 Think V2 — Institutional Quant Platform",
833
  theme=gr.themes.Soft(
834
+ primary_hue="blue", secondary_hue="indigo", neutral_hue="slate",
 
 
835
  font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"]
836
  ),
837
  css="""
 
841
  .tab-nav { background: #0d1117 !important; border-bottom: 1px solid #30363d !important; }
842
  .tab-nav button { color: #8b949e !important; background: transparent !important; border: none !important; }
843
  .tab-nav button.selected { color: #58a6ff !important; border-bottom: 2px solid #58a6ff !important; }
 
844
  input, textarea, select { background: #21262d !important; color: #e6edf3 !important; border: 1px solid #30363d !important; }
845
  button.primary { background: linear-gradient(135deg, #1f6feb, #58a6ff) !important; color: white !important; border: none !important; border-radius: 8px !important; font-weight: 600 !important; }
846
  button.secondary { background: #21262d !important; color: #58a6ff !important; border: 1px solid #30363d !important; border-radius: 8px !important; }
 
 
 
 
 
 
 
 
847
  .title-bar { text-align: center; padding: 24px 0; }
848
  .title-bar h1 { font-size: 2.8em; font-weight: 800; margin: 0; background: linear-gradient(90deg, #58a6ff, #a371f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
849
  .title-bar p { color: #8b949e; font-size: 1.1em; margin-top: 8px; }
 
852
  .badge-api { background: linear-gradient(135deg, #1f6feb, #a371f7); color: white; }
853
  .badge-data { background: #238636; color: white; }
854
  .badge-alpha { background: #8957e5; color: white; }
855
+ .k2-active { text-align: center; padding: 8px; margin: 8px 0; border-radius: 8px; font-size: 0.9em; background: rgba(35,134,54,0.2); color: #3fb950; border: 1px solid #238636; }
856
+ .k2-inactive { text-align: center; padding: 8px; margin: 8px 0; border-radius: 8px; font-size: 0.9em; background: rgba(209,36,47,0.2); color: #f85149; border: 1px solid #da3633; }
 
857
  """
858
  ) as demo:
 
 
859
  gr.HTML("""
860
  <div class="title-bar">
861
  <h1>🔥 AlphaForge x K2 Think V2</h1>
862
+ <p>Institutional-Grade Quantitative Analysis Platform - Powered by MBZUAI's State-of-the-Art Reasoning Model</p>
863
  </div>
864
  <div class="badge-row">
865
  <span class="badge badge-api">🤖 K2 Think V2</span>
866
+ <span class="badge badge-data">📊 Multi-Market</span>
867
+ <span class="badge badge-alpha">🎯 AI Alpha</span>
868
+ <span class="badge badge-data">📐 Options</span>
869
+ <span class="badge badge-alpha">🔗 Pairs</span>
870
+ <span class="badge badge-api">🌍 Macro</span>
871
  </div>
872
  """)
873
 
874
+ k2_cls = "k2-active" if K2_API_KEY else "k2-inactive"
875
+ k2_txt = "✅ K2 Think V2 API Connected" if K2_API_KEY else "⚠️ K2 Think V2 Not Configured — Add K2_API_KEY in Space Settings > Repository Secrets"
876
+ gr.HTML(f'<div class="{k2_cls}">{k2_txt}</div>')
 
 
 
877
 
 
878
  with gr.Tab("📈 Technical Analysis"):
879
  with gr.Row():
880
  with gr.Column(scale=1):
881
+ mkt_select = gr.Dropdown(label="🌍 Market", choices=list(MARKET_PRESETS.keys()), value="🇺🇸 US Equities")
 
 
 
882
  ticker_in = gr.Textbox(label="Ticker", value="AAPL", placeholder="e.g., AAPL, BTC-USD, EURUSD=X")
 
883
  period_in = gr.Dropdown(label="Period", choices=["1mo","3mo","6mo","1y","2y","5y"], value="6mo")
884
  interval_in = gr.Dropdown(label="Interval", choices=["1d","1wk","1mo"], value="1d")
885
  analyze_btn = gr.Button("🔍 Analyze", variant="primary", size="lg")
 
898
  with gr.Row():
899
  ai_out = gr.Textbox(label="🤖 K2 Think V2 Analysis", lines=30, max_lines=50, show_copy_button=True)
900
 
901
+ analyze_btn.click(fn=analyze_stock, inputs=[ticker_in, mkt_select, period_in, interval_in],
902
+ outputs=[chart1, chart2, chart3, chart4, chart5, chart6, summary_out])
903
+ ai_btn.click(fn=ai_analyze_stock, inputs=[ticker_in, mkt_select, period_in, interval_in],
904
+ outputs=[ai_out])
 
 
 
 
 
 
905
 
 
906
  with gr.Tab("💼 Portfolio Optimizer"):
907
  with gr.Row():
908
  with gr.Column(scale=1):
909
  port_in = gr.Textbox(label="Tickers (comma-separated)", value="AAPL, MSFT, GOOGL, AMZN, NVDA")
910
  port_period = gr.Dropdown(label="Lookback", choices=["6mo","1y","2y","3y"], value="1y")
911
+ opt_btn = gr.Button("🎯 Optimize", variant="primary", size="lg")
912
+ ai_port_btn = gr.Button("🤖 AI Portfolio Advice", variant="secondary", size="lg")
913
  with gr.Column(scale=2):
914
  port_md = gr.Markdown()
915
  with gr.Row():
 
919
  weights_df = gr.DataFrame(label="Optimal Weights", interactive=False)
920
  with gr.Row():
921
  ai_port_out = gr.Textbox(label="🤖 AI Portfolio Advice", lines=25, max_lines=40, show_copy_button=True)
 
922
  opt_btn.click(fn=optimize_portfolio, inputs=[port_in, port_period], outputs=[frontier_plot, corr_plot, weights_df, port_md])
923
  ai_port_btn.click(fn=ai_portfolio, inputs=[port_in, port_period], outputs=[ai_port_out])
924
 
 
925
  with gr.Tab("🔗 Pairs Trading"):
926
  with gr.Row():
927
  with gr.Column(scale=1):
 
934
  with gr.Row():
935
  pair_chart = gr.Plot(label="Spread Analysis")
936
  pair_scatter = gr.Plot(label="Price Relationship")
 
937
  pair_btn.click(fn=analyze_pair, inputs=[pair_a, pair_b, pair_period], outputs=[pair_chart, pair_scatter, pair_md])
938
 
 
939
  with gr.Tab("📐 Options Pricing"):
940
  with gr.Row():
941
  with gr.Column(scale=1):
 
951
  with gr.Row():
952
  greeks_plot = gr.Plot(label="Greeks Analysis")
953
  opt_pl = gr.DataFrame(label="P/L Scenarios", interactive=False)
954
+ opt_calc_btn.click(fn=analyze_options, inputs=[opt_ticker, opt_strike, opt_days, opt_rfr, opt_vol, opt_type],
955
+ outputs=[greeks_plot, opt_pl, opt_md])
 
 
 
 
956
 
 
957
  with gr.Tab("🌍 Macro Analysis"):
958
  with gr.Row():
959
  macro_btn = gr.Button("🌍 Analyze Global Macro (K2 Think V2)", variant="primary", size="lg")
960
  with gr.Row():
961
  macro_out = gr.Textbox(label="🤖 K2 Think V2 Macro Analysis", lines=40, max_lines=60, show_copy_button=True)
 
962
  macro_btn.click(fn=ai_macro, outputs=[macro_out])
963
 
 
964
  with gr.Tab("💬 K2 Think V2 Chat"):
965
  gr.Markdown("## Direct Chat with K2 Think V2")
966
+ gr.Markdown("Ask any financial question - strategy, market analysis, quant interview prep, portfolio advice.")
967
  with gr.Row():
968
  chat_in = gr.Textbox(label="Your Question", placeholder="e.g., 'Explain gamma scalping with a real trade example'", lines=4, scale=4)
969
  chat_temp = gr.Slider(label="Temp", minimum=0, maximum=1, value=0.4, step=0.1, scale=1)
 
971
  chat_out = gr.Textbox(label="🤖 Response", lines=30, max_lines=50, show_copy_button=True)
972
  chat_btn.click(fn=ai_chat, inputs=[chat_in, chat_temp], outputs=[chat_out])
973
 
 
974
  with gr.Tab("ℹ️ About & Setup"):
975
  gr.Markdown(f"""
976
  ## AlphaForge x K2 Think V2
 
982
  |---------|-------------|
983
  | **📈 Technical Analysis** | 18+ indicators: RSI, MACD, Bollinger, VWAP, Stochastic, ADX, Ichimoku, ATR, MFI, OBV |
984
  | **🌍 Multi-Market** | US, EU, UK, DE, JP, CN, IN equities + Crypto + Forex + Commodities + Indices |
985
+ | **💼 Portfolio** | Mean-variance optimization, efficient frontier, correlation matrix |
986
  | **🔗 Pairs Trading** | Cointegration analysis, hedge ratio, half-life, Z-score signals |
987
  | **📐 Options Pricing** | Black-Scholes + full Greeks (Delta, Gamma, Theta, Vega, Rho), P/L scenarios |
988
  | **🌍 Macro Analysis** | Global cross-asset regime analysis via K2 Think V2 |
 
1009
  return demo
1010
 
1011
  if __name__ == "__main__":
 
 
 
1012
  demo = build_app()
1013
+ demo.queue().launch(server_name="0.0.0.0", server_port=7860)