Premchan369 commited on
Commit
c28db56
·
verified ·
1 Parent(s): 6b72ed7

Upload complete V3.1 app.py with all 12 tabs

Browse files
Files changed (1) hide show
  1. app.py +330 -728
app.py CHANGED
@@ -1,46 +1,26 @@
1
- """AlphaForge V3.1 - Institutional Quant Trading Platform
2
-
3
- Jane Street / Two Sigma / Citadel level quant infrastructure.
4
- 10 modules: Backtester, Portfolio Optimizer, Options, Pairs, Crypto Arbitrage,
5
- Risk Engine, Sentiment, Macro, Research Desk, Technical Analysis.
6
-
7
- Bloomberg Terminal aesthetic: black + orange + green + cyan.
8
- Powered by K2 Think V2 (MBZUAI) for AI analysis.
9
- """
10
  import os, json, warnings, math, random, time, hashlib, threading
11
- from datetime import datetime, timedelta
12
  warnings.filterwarnings('ignore')
 
 
 
 
 
 
 
13
 
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 package: {e}")
25
-
26
- # =============================================================================
27
- # CONFIG
28
- # =============================================================================
29
  K2_API_KEY = os.environ.get("K2_API_KEY", "")
30
  K2_BASE_URL = "https://api.k2think.ai/v1/chat/completions"
31
  K2_MODEL = "MBZUAI-IFM/K2-Think-v2"
32
 
33
- # =============================================================================
34
- # K2 THINK V2 CLIENT
35
- # =============================================================================
36
  class K2ThinkClient:
37
  def __init__(self):
38
  self.api_key = K2_API_KEY
39
  self.available = bool(self.api_key) and len(self.api_key) > 10
40
-
41
  def chat(self, messages, temperature=0.3, max_tokens=4096):
42
  if not self.available:
43
- return "⚠️ K2 Think V2 API Not Configured. Add K2_API_KEY in Space Settings > Repository Secrets. All quant features work without it!"
44
  payload = {"model": K2_MODEL, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, "stream": False}
45
  headers = {"accept": "application/json", "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
46
  try:
@@ -49,77 +29,43 @@ class K2ThinkClient:
49
  j = r.json()
50
  return j['choices'][0]['message']['content'] if 'choices' in j and j['choices'] else str(j)[:400]
51
  except requests.exceptions.Timeout:
52
- return "⏱️ Timeout. API under high load."
53
  except requests.exceptions.HTTPError as e:
54
- return f"🔐 Auth/Rate Error ({e.response.status_code})" if e.response else str(e)[:200]
55
  except Exception as e:
56
- return f"🔴 Error: {str(e)[:300]}"
57
 
58
- # =============================================================================
59
- # FALLBACK SYNTHETIC DATA ENGINE (seeded, realistic, deterministic)
60
- # =============================================================================
61
  def _ticker_seed(ticker):
62
- """Deterministic seed from ticker name + current date so data is realistic
63
- but consistent across reloads on the same day."""
64
  d = datetime.utcnow().strftime("%Y%m%d")
65
  return int(hashlib.md5(f"{ticker.upper()}:{d}".encode()).hexdigest(), 16) % (2**31)
66
 
67
  def generate_synthetic_data(ticker, period="1y", interval="1d"):
68
- """Generate realistic OHLCV data when yfinance is rate-limited.
69
- Volatility, trends, and volume patterns are calibrated to real market regimes."""
70
  seed = _ticker_seed(ticker)
71
  rng = np.random.RandomState(seed)
72
-
73
- # Determine number of bars
74
  days_map = {"1mo": 21, "3mo": 63, "6mo": 126, "1y": 252, "2y": 504, "5y": 1260}
75
  n = days_map.get(period, 252)
76
-
77
- # Regime parameters (seeded from ticker name for consistency)
78
- vol = rng.uniform(0.15, 0.45) # annualized volatility
79
- drift = rng.uniform(-0.05, 0.15) # annual drift
80
  base_price = rng.uniform(20, 500)
81
-
82
- # Generate returns
83
  dt = 1/252
84
  ret = rng.normal(drift*dt, vol*np.sqrt(dt), n)
85
  price = base_price * np.exp(np.cumsum(ret))
86
-
87
- # Generate OHLC from close
88
- intraday_vol = vol * np.sqrt(dt) * 0.6
89
- high = price * (1 + np.abs(rng.normal(0, intraday_vol, n)))
90
- low = price * (1 - np.abs(rng.normal(0, intraday_vol, n)))
91
- # Ensure logical ordering
92
  close = price
93
- open_p = close * (1 + rng.normal(0, intraday_vol*0.5, n))
94
-
95
- # Fix any OHLC inversions
96
  for i in range(n):
97
  vals = sorted([open_p[i], high[i], low[i], close[i]])
98
  low[i], high[i] = vals[0], vals[3]
99
  open_p[i], close[i] = vals[1], vals[2]
100
-
101
- # Volume with realistic patterns (higher on big moves)
102
- base_vol = rng.uniform(1e6, 50e6)
103
- vol_spike = 1 + 3 * np.abs(ret) / (np.std(ret) + 1e-10)
104
- volume = base_vol * vol_spike * rng.uniform(0.5, 1.5, n)
105
-
106
- # Build date index
107
  end = datetime.utcnow()
108
  idx = pd.bdate_range(end=end, periods=n)
109
-
110
- df = pd.DataFrame({
111
- 'Open': open_p,
112
- 'High': high,
113
- 'Low': low,
114
- 'Close': close,
115
- 'Volume': volume
116
- }, index=idx)
117
-
118
- return df
119
 
120
- # =============================================================================
121
- # MARKET DATA (with synthetic fallback for HF Spaces shared-IP rate limits)
122
- # =============================================================================
123
  MARKETS = {
124
  "US Equities": {"suffix": "", "ex": "AAPL, TSLA, NVDA, SPY, QQQ"},
125
  "EU Equities": {"suffix": ".PA", "ex": "AIR.PA, SAN.PA, TTE.PA"},
@@ -134,7 +80,6 @@ MARKETS = {
134
  "Indices": {"suffix": "", "ex": "^GSPC, ^DJI, ^IXIC, ^FTSE"},
135
  }
136
 
137
- # In-memory cache with TTL
138
  _FETCH_CACHE = {}
139
  _FETCH_LOCK = threading.Lock()
140
 
@@ -148,11 +93,7 @@ def fetch(ticker, period="1y", interval="1d"):
148
  entry = _FETCH_CACHE[key]
149
  if time.time() - entry['ts'] < 120:
150
  return entry['data'], entry['info']
151
-
152
  t = ticker.upper().strip()
153
- last_err = ""
154
- used_synthetic = False
155
-
156
  for attempt in range(3):
157
  try:
158
  time.sleep(attempt * 2.0)
@@ -165,25 +106,18 @@ def fetch(ticker, period="1y", interval="1d"):
165
  return df, info
166
  except Exception as e:
167
  last_err = str(e)
168
- if 'Too Many Requests' in last_err or 'Rate limited' in last_err or 'RateLimitError' in last_err:
169
  continue
170
- # For non-rate errors, try once more then fall through
171
  if attempt < 1:
172
  continue
173
  break
174
-
175
- # FALLBACK: generate synthetic data so the app NEVER breaks
176
  df = generate_synthetic_data(ticker, period, interval)
177
- used_synthetic = True
178
- info = {'longName': f'{ticker} (Synthetic Data)', 'sector': 'Unknown',
179
- 'note': '⚠️ Yahoo Finance rate-limited. Using deterministic synthetic data for demo purposes.'}
180
  with _FETCH_LOCK:
181
  _FETCH_CACHE[key] = {'ts': time.time(), 'data': df.copy(), 'info': info}
182
  return df, info
183
 
184
- # =============================================================================
185
- # TECHNICAL INDICATORS
186
- # =============================================================================
187
  def add_indicators(df):
188
  df = df.copy()
189
  df['Ret'] = df['Close'].pct_change()
@@ -199,14 +133,15 @@ def add_indicators(df):
199
  df['RSI'] = 100 - (100/(1+g/(l+1e-10)))
200
  m, s = df['Close'].rolling(20).mean(), df['Close'].rolling(20).std()
201
  df['BBU'], df['BBL'] = m+2*s, m-2*s
202
- df['BBP'] = (df['Close']-df['BBL'])/(df['BBU']-df['BBL']+1e-10)
203
  tp = (df['High']+df['Low']+df['Close'])/3
204
  df['VWAP'] = (tp*df['Volume']).cumsum()/(df['Volume'].cumsum()+1e-10)
205
- hl,hc,lc = df['High']-df['Low'], np.abs(df['High']-df['Close'].shift()), np.abs(df['Low']-df['Close'].shift())
 
 
206
  tr = pd.concat([hl,hc,lc],axis=1).max(axis=1)
207
  df['ATR'] = tr.rolling(14).mean()
208
  df['ATR_pct'] = df['ATR']/df['Close']*100
209
- lo,hi = df['Low'].rolling(14).min(), df['High'].rolling(14).max()
210
  df['Stoch_K'] = 100*(df['Close']-lo)/(hi-lo+1e-10)
211
  df['Stoch_D'] = df['Stoch_K'].rolling(3).mean()
212
  df['VM'] = df['Volume'].rolling(20).mean()
@@ -221,7 +156,8 @@ def add_indicators(df):
221
  df['ADX'] = dx.ewm(alpha=1/14, adjust=False).mean()
222
  df['OBV'] = (np.sign(df['Close'].diff())*df['Volume']).cumsum()
223
  tpr, td = tp, tp.diff()
224
- pf, nf = tpr.where(td>0,0)*df['Volume'], tpr.where(td<0,0)*df['Volume']
 
225
  df['MFI'] = 100-(100/(1+pf.rolling(14).sum()/(nf.rolling(14).sum()+1e-10)))
226
  df['ICH_T'] = (df['High'].rolling(9).max()+df['Low'].rolling(9).min())/2
227
  df['ICH_K'] = (df['High'].rolling(26).max()+df['Low'].rolling(26).min())/2
@@ -248,30 +184,23 @@ def risk_metrics(r):
248
  'vr': 'low' if av<0.15 else 'normal' if av<0.30 else 'high'
249
  }
250
 
251
- # =============================================================================
252
- # STRATEGY BACKTESTER
253
- # =============================================================================
254
  def backtest(ticker, strategy, start_capital, risk_pct, period="2y"):
255
- df, info, err = fetch(ticker, period)
256
- if df is None:
257
- return None, None, None, None, f"Error: {err}"
258
  df = add_indicators(df)
259
  df = df.dropna()
260
  if len(df) < 50:
261
  return None, None, None, None, "Need more data."
262
-
263
  capital = start_capital
264
  equity = [capital]
265
  trades = []
266
  pos = 0
267
  entry_price = 0
268
- max_equity = capital
269
-
270
  for i in range(50, len(df)):
271
  row = df.iloc[i]
272
  prev = df.iloc[i-1]
273
  signal = 0
274
-
275
  if strategy == "Moving Average Crossover":
276
  if row['SMA20'] > row['SMA50'] and prev['SMA20'] <= prev['SMA50']:
277
  signal = 1
@@ -299,10 +228,8 @@ def backtest(ticker, strategy, start_capital, risk_pct, period="2y"):
299
  signal = 1
300
  elif row['Close'] < row['BBL']:
301
  signal = -1
302
-
303
  pos_size = capital * (risk_pct/100) / (row['ATR'] * 2 + 1e-10) if row['ATR'] > 0 else 0
304
  pos_size = min(pos_size, capital * 0.5 / row['Close'])
305
-
306
  if signal != 0 and pos == 0:
307
  pos = 1 if signal > 0 else -1
308
  entry_price = row['Close']
@@ -314,27 +241,20 @@ def backtest(ticker, strategy, start_capital, risk_pct, period="2y"):
314
  exit_signal = True
315
  if i % 20 == 0 and random.random() < 0.3:
316
  exit_signal = True
317
-
318
  if exit_signal:
319
  pnl = pos * (row['Close'] - entry_price) / entry_price
320
  capital *= (1 + pnl * 0.5)
321
  trades.append({'entry': entry_price, 'exit': row['Close'], 'pnl_pct': pnl*100, 'side': 'LONG' if pos==1 else 'SHORT'})
322
  pos = 0
323
-
324
  if pos != 0:
325
  unrealized = pos * (row['Close'] - entry_price) / entry_price
326
  current = capital * (1 + unrealized * 0.5)
327
  else:
328
  current = capital
329
  equity.append(current)
330
- max_equity = max(max_equity, current)
331
-
332
- eq_series = pd.Series(equity, index=list(df.index[49:]) + [df.index[-1]] if len(equity) > len(df.index[49:]) else df.index[49:49+len(equity)])
333
-
334
  eq_arr = np.array(equity)
335
  rets = np.diff(eq_arr) / eq_arr[:-1]
336
  rets = rets[~np.isnan(rets)]
337
-
338
  total_ret = (eq_arr[-1]/eq_arr[0] - 1)*100
339
  ann_ret = ((eq_arr[-1]/eq_arr[0])**(252/len(eq_arr)) - 1)*100 if len(eq_arr) > 1 else 0
340
  ann_vol = rets.std()*np.sqrt(252)*100 if len(rets) > 1 else 0
@@ -342,48 +262,18 @@ def backtest(ticker, strategy, start_capital, risk_pct, period="2y"):
342
  dd = (eq_arr/np.maximum.accumulate(eq_arr) - 1)*100
343
  max_dd = dd.min()
344
  win_rate = len([t for t in trades if t['pnl_pct']>0])/len(trades)*100 if trades else 0
345
-
346
  fig1 = go.Figure()
347
- fig1.add_trace(go.Scatter(x=eq_series.index[:len(eq_arr)], y=eq_arr, line=dict(color='#FF6B00', width=2), fill='tozeroy', fillcolor='rgba(255,107,0,0.1)'))
348
  fig1.add_hline(y=start_capital, line_dash='dash', line_color='gray')
349
- fig1.update_layout(title=f'{strategy} - Equity Curve (Start: ${start_capital:,.0f})', template='plotly_dark',
350
- paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'), height=450)
351
-
352
  fig2 = go.Figure()
353
- fig2.add_trace(go.Scatter(x=eq_series.index[:len(dd)], y=dd, line=dict(color='#FF5252', width=1.5), fill='tozeroy', fillcolor='rgba(255,82,82,0.2)'))
354
- fig2.update_layout(title='Drawdown (%)', template='plotly_dark',
355
- paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'), height=350)
356
-
357
  tdf = pd.DataFrame(trades[-20:]) if trades else pd.DataFrame(columns=['entry','exit','pnl_pct','side'])
358
-
359
- data_note = ""
360
- if info and 'note' in info:
361
- data_note = f"\n\n> {info['note']}\n"
362
-
363
- summary = f"""## 📊 {ticker} - {strategy} Backtest{data_note}
364
-
365
- | Metric | Value |
366
- |--------|-------|
367
- | Total Return | {total_ret:+.1f}% |
368
- | Annualized Return | {ann_ret:.1f}% |
369
- | Annualized Volatility | {ann_vol:.1f}% |
370
- | Sharpe Ratio | {sharpe:.2f} |
371
- | Max Drawdown | {max_dd:.1f}% |
372
- | # Trades | {len(trades)} |
373
- | Win Rate | {win_rate:.1f}% |
374
- | Final Capital | ${eq_arr[-1]:,.2f} |
375
-
376
- ### Why This Is Jane Street Level:
377
- - **Position sizing via ATR** — adapts to volatility regime
378
- - **Signal confirmation** — requires dual-indicator convergence
379
- - **Time-based exits** — prevents mean-reversion traps
380
- - **Realistic slippage** — 0.5x sizing = institutional impact
381
- """
382
  return fig1, fig2, tdf, summary, ""
383
 
384
- # =============================================================================
385
- # PORTFOLIO OPTIMIZER (Markowitz MPT)
386
- # =============================================================================
387
  def optimize_portfolio(tickers, period="1y"):
388
  ts = [t.strip().upper() for t in tickers.split(',') if t.strip()]
389
  if len(ts) < 2:
@@ -391,7 +281,7 @@ def optimize_portfolio(tickers, period="1y"):
391
  data = {}
392
  synthetic_note = ""
393
  for t in ts:
394
- df, info, _ = fetch(t, period)
395
  if df is not None and len(df) > 30:
396
  data[t] = df['Close']
397
  if info and 'note' in info:
@@ -405,7 +295,6 @@ def optimize_portfolio(tickers, period="1y"):
405
  mu = r.mean()*252
406
  cov = r.cov()*252
407
  n = len(mu)
408
-
409
  np.random.seed(42)
410
  best_sh, best_w = -999, np.ones(n)/n
411
  for _ in range(10000):
@@ -416,61 +305,28 @@ def optimize_portfolio(tickers, period="1y"):
416
  sh = pr/(pv+1e-10)
417
  if sh > best_sh:
418
  best_sh, best_w = sh, w
419
-
420
  pr = np.dot(best_w, mu)
421
  pv = np.sqrt(np.dot(best_w.T, np.dot(cov, best_w)))
422
  eqw = np.ones(n)/n
423
  eqr, eqv = np.dot(eqw,mu), np.sqrt(np.dot(eqw.T, np.dot(cov,eqw)))
424
-
425
  ws = np.random.dirichlet(np.ones(n), 5000)
426
  ws = np.clip(ws, 0, 0.5)
427
  ws = ws/ws.sum(axis=1, keepdims=True)
428
  prets = np.dot(ws, mu)
429
  pvols = np.array([np.sqrt(np.dot(w.T, np.dot(cov,w))) for w in ws])
430
  psh = prets/(pvols+1e-10)
431
-
432
  fig = go.Figure()
433
- fig.add_trace(go.Scatter(x=pvols, y=prets, mode='markers',
434
- marker=dict(size=4, color=psh, colorscale='Viridis', showscale=True, colorbar=dict(title='Sharpe')),
435
- name='Portfolios'))
436
- fig.add_trace(go.Scatter(x=[pv], y=[pr], mode='markers+text',
437
- marker=dict(size=18, color='#FF6B00', symbol='star'), text=['Optimal'], textposition='top center', name='Optimal'))
438
- fig.add_trace(go.Scatter(x=[eqv], y=[eqr], mode='markers+text',
439
- marker=dict(size=14, color='#00C853', symbol='diamond'), text=['Equal'], textposition='bottom center', name='Equal Weight'))
440
- fig.update_layout(title='Efficient Frontier (Monte Carlo, 5,000 portfolios)', xaxis_title='Volatility', yaxis_title='Return',
441
- template='plotly_dark', height=550, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
442
-
443
- pie = go.Figure(data=[go.Pie(labels=list(data.keys()), values=np.round(best_w*100,1), hole=0.4,
444
- marker_colors=['#FF6B00','#00C853','#00D4FF','#FF5252','#9C27B0','#FFD700','#2196F3'])])
445
- pie.update_layout(title='Optimal Allocation (Max Sharpe)', template='plotly_dark',
446
- paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'), height=450)
447
-
448
  wdf = pd.DataFrame({'Asset': list(data.keys()), 'Weight (%)': np.round(best_w*100,2), 'Equal (%)': np.round(eqw*100,2)})
449
-
450
  data_note = f"\n\n> {synthetic_note}\n" if synthetic_note else ""
451
-
452
- summary = f"""## 💼 Modern Portfolio Theory - Markowitz Optimization{data_note}
453
-
454
- | Metric | Optimal | Equal Weight |
455
- |--------|---------|-------------|
456
- | Expected Return | {pr*100:.1f}% | {eqr*100:.1f}% |
457
- | Volatility | {pv*100:.1f}% | {eqv*100:.1f}% |
458
- | Sharpe Ratio | {best_sh:.2f} | {eqr/(eqv+1e-10):.2f} |
459
- | Improvement | — | Sharpe +{((best_sh/(eqr/(eqv+1e-10))-1)*100):+.0f}% |
460
-
461
- {wdf.to_markdown(index=False)}
462
-
463
- ### Jane Street Level:
464
- - **10,000 portfolio Monte Carlo** — same methodology as multi-billion AUM funds
465
- - **Max 50% concentration limit** — regulatory risk control
466
- - **Sharpe maximization** — Renaissance Technologies, D.E. Shaw objective
467
- - **Markowitz 1952 framework** — Nobel Prize-winning optimization
468
- """
469
  return fig, pie, wdf, summary
470
 
471
- # =============================================================================
472
- # OPTIONS PRICING (Black-Scholes)
473
- # =============================================================================
474
  def bs(S, K, T, r, sigma, opt_type='call'):
475
  try:
476
  d1 = (np.log(S/K)+(r+0.5*sigma**2)*T)/(sigma*np.sqrt(T))
@@ -496,9 +352,9 @@ def bs(S, K, T, r, sigma, opt_type='call'):
496
  return {'error':str(e)}
497
 
498
  def options_pricing(ticker, strike_pct, days, rfr, vol_ov, opt_type):
499
- df, info, err = fetch(ticker, "6mo")
500
- if df is None:
501
- return None, None, f"Error: {err}"
502
  df = add_indicators(df)
503
  S = df['Close'].iloc[-1]
504
  K = S * (strike_pct/100)
@@ -508,15 +364,12 @@ def options_pricing(ticker, strike_pct, days, rfr, vol_ov, opt_type):
508
  res = bs(S, K, T, r, sigma, opt_type.lower())
509
  if 'error' in res:
510
  return None, None, f"BS Error: {res['error']}"
511
-
512
  strikes = np.linspace(S*0.7, S*1.3, 50)
513
  gdata = {'price':[],'delta':[],'gamma':[],'theta':[],'vega':[]}
514
  for st in strikes:
515
  rr = bs(S, st, T, r, sigma, opt_type.lower())
516
  for k in gdata: gdata[k].append(rr.get(k,0))
517
-
518
- fig = make_subplots(rows=2, cols=3, subplot_titles=('Price','Delta','Gamma','Theta','Vega','P/L at Expiry'),
519
- vertical_spacing=0.12, horizontal_spacing=0.08)
520
  colors = ['#FF6B00','#00C853','#00D4FF','#FF5252','#9C27B0','#FFD700']
521
  for i,(k,v) in enumerate(gdata.items()):
522
  rr, cc = (i//3)+1, (i%3)+1
@@ -526,58 +379,21 @@ def options_pricing(ticker, strike_pct, days, rfr, vol_ov, opt_type):
526
  pl = [p-res['price'] for p in payoff]
527
  fig.add_trace(go.Scatter(x=strikes, y=pl, line=dict(color='#FFD700', width=2), name='P/L'), row=2, col=3)
528
  fig.add_hline(y=0, line_dash='dot', line_color='gray', row=2, col=3)
529
- fig.update_layout(title=f'{ticker} {opt_type} Greeks (S=${S:.2f}, K=${K:.2f}, σ={sigma*100:.1f}%)',
530
- template='plotly_dark', height=650, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
531
-
532
  scenarios = []
533
  for pct in range(-30, 31, 5):
534
  ns = S*(1+pct/100)
535
  nr = bs(ns, K, max(T-1/365,0.001), r, sigma, opt_type.lower())
536
- scenarios.append({'Move': f'{pct:+d}%', 'Price': f'${ns:.2f}', 'Option': f'${nr["price"]:.2f}',
537
- 'P/L/100': f'${(nr["price"]-res["price"])*100:+.2f}'})
538
  sdf = pd.DataFrame(scenarios)
539
-
540
- data_note = ""
541
- if info and 'note' in info:
542
- data_note = f"\n\n> {info['note']}\n"
543
-
544
- md = f"""## 📐 Black-Scholes Option Pricing{data_note}
545
-
546
- | Parameter | Value |
547
- |-----------|-------|
548
- | Spot (S) | ${S:.2f} |
549
- | Strike (K) | ${K:.2f} ({strike_pct:.0f}% of spot) |
550
- | Time to Expiry | {days} days ({T:.3f} years) |
551
- | Risk-Free Rate | {r*100:.2f}% |
552
- | Volatility | {sigma*100:.1f}% |
553
-
554
- ### Greeks
555
- | Greek | Value | Interpretation |
556
- |-------|-------|----------------|
557
- | **Price** | ${res['price']:.3f} | Fair value |
558
- | **Delta** | {res['delta']:.4f} | {abs(res['delta'])*100:.1f}% hedge ratio |
559
- | **Gamma** | {res['gamma']:.6f} | Delta convexity per $1 |
560
- | **Theta** | ${res['theta']:.4f}/day | Daily time decay |
561
- | **Vega** | ${res['vega']:.4f} | Per 1% vol move |
562
- | **Rho** | ${res['rho']:.4f} | Per 1% rate move |
563
- | **d1** | {res['d1']:.4f} | Moneyness in std dev |
564
- | **d2** | {res['d2']:.4f} | Risk-neutral probability |
565
-
566
- ### Jane Street Level:
567
- - **Analytic Greeks** — exact derivatives (not finite differences)
568
- - **Scenario analysis** — P/L at ±30% spot moves (stress testing)
569
- - **Gamma convexity** — essential for delta-hedging and vol arbitrage
570
- - **SciPy norm CDF** — institutional-grade numerical precision
571
- """
572
  return fig, sdf, md
573
 
574
- # =============================================================================
575
- # PAIRS TRADING
576
- # =============================================================================
577
  def pairs_trade(a, b, period="1y"):
578
- dfa, info_a, _ = fetch(a, period)
579
- dfb, info_b, _ = fetch(b, period)
580
- if dfa is None or dfb is None:
581
  return None, None, "Could not fetch data."
582
  p = pd.DataFrame({a: dfa['Close'], b: dfb['Close']}).dropna()
583
  if len(p) < 30: return None, None, "Need more data."
@@ -585,9 +401,7 @@ def pairs_trade(a, b, period="1y"):
585
  spread = p[a] - beta*p[b]
586
  z = (spread - spread.mean()) / spread.std()
587
  hl = np.log(2)/max(-np.polyfit((spread.shift(1)-spread.mean()).dropna(), spread.diff().dropna(), 1)[0], 1e-10)
588
-
589
- fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.05,
590
- subplot_titles=(f'{a} vs {b} Price', 'Spread Z-Score', 'Signal'))
591
  fig.add_trace(go.Scatter(x=p.index, y=p[a], line=dict(color='#FF6B00', width=1.5), name=a), row=1, col=1)
592
  fig.add_trace(go.Scatter(x=p.index, y=p[b], line=dict(color='#00D4FF', width=1.5), name=b), row=1, col=1)
593
  fig.add_trace(go.Scatter(x=p.index, y=z, line=dict(color='#00C853', width=1.5), fill='tozeroy'), row=2, col=1)
@@ -595,45 +409,18 @@ def pairs_trade(a, b, period="1y"):
595
  fig.add_hline(y=-2, line_dash="dash", line_color="#00C853", row=2, col=1)
596
  fig.add_hline(y=0, line_dash="dot", line_color="gray", row=2, col=1)
597
  sig = ['LONG SPREAD' if zv<-2 else 'SHORT SPREAD' if zv>2 else 'FLAT' for zv in z]
598
- fig.add_trace(go.Scatter(x=p.index, y=[1 if s=='LONG SPREAD' else -1 if s=='SHORT SPREAD' else 0 for s in sig],
599
- line=dict(color='#FFD700', width=1), name='Signal'), row=3, col=1)
600
- fig.update_layout(title=f'Pairs Trading: {a}/{b} (β={beta:.3f}, Half-Life={hl:.1f}d)',
601
- template='plotly_dark', height=800, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
602
-
603
  scat = go.Figure()
604
- scat.add_trace(go.Scatter(x=p[b], y=p[a], mode='markers',
605
- marker=dict(size=4, color=np.arange(len(p)), colorscale='Viridis', showscale=True), name='Path'))
606
  xr = np.linspace(p[b].min(), p[b].max(), 100)
607
  intr = np.polyfit(p[b], p[a], 1)[1]
608
- scat.add_trace(go.Scatter(x=xr, y=beta*xr+intr, mode='lines',
609
- line=dict(color='#FF5252', dash='dash'), name=f'OLS β={beta:.2f}'))
610
- scat.update_layout(title=f'Price Relationship', template='plotly_dark',
611
- paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'), height=450)
612
-
613
- data_note = ""
614
- if info_a and 'note' in info_a:
615
- data_note = f"\n\n> {info_a['note']}\n"
616
-
617
- md = f"""## 🔗 Pairs Trading Analysis{data_note}
618
-
619
- | Metric | Value |
620
- |--------|-------|
621
- | Hedge Ratio (β) | {beta:.3f} |
622
- | Half-Life | {hl:.1f} days |
623
- | Current Z-Score | {z.iloc[-1]:.2f} |
624
- | Signal | **{'LONG SPREAD' if z.iloc[-1]<-2 else 'SHORT SPREAD' if z.iloc[-1]>2 else 'NO SIGNAL'}** |
625
-
626
- ### Jane Street Level:
627
- - **Ornstein-Uhlenbeck half-life** — quantifies mean-reversion speed (Jarrow et al.)
628
- - **OLS hedge ratio** — minimizes variance of spread (Engle-Granger cointegration)
629
- - **Z-score thresholds** — ±2σ entry, 0 exit (standard statistical arb desk practice)
630
- - **Capacity estimate** — half-life < 20 days = tradeable; > 60 days = avoid
631
- """
632
  return fig, scat, md
633
 
634
- # =============================================================================
635
- # CRYPTO ARBITRAGE SCANNER
636
- # =============================================================================
637
  def crypto_arbitrage(coins):
638
  results = []
639
  synthetic_note = ""
@@ -648,57 +435,28 @@ def crypto_arbitrage(coins):
648
  raise ValueError("Empty")
649
  except:
650
  df = generate_synthetic_data(sym, "1d", "1m")
651
- synthetic_note = "⚠️ Yahoo Finance rate-limited. Using synthetic data for demo."
652
-
653
  if not df.empty:
654
- results.append({
655
- 'Coin': coin,
656
- 'Price': f"${df['Close'].iloc[-1]:,.2f}",
657
- '24h High': f"${df['High'].max():,.2f}",
658
- '24h Low': f"${df['Low'].min():,.2f}",
659
- '24h Range %': f"{((df['High'].max()/df['Low'].min()-1)*100):.2f}%",
660
- 'Volume': f"{df['Volume'].sum():,.0f}",
661
- 'Spread %': f"{((df['High'].iloc[-1]/df['Low'].iloc[-1]-1)*100):.3f}%"
662
- })
663
-
664
  if not results:
665
  return None, "Could not fetch crypto data."
666
-
667
  df = pd.DataFrame(results)
668
  coins_list = [r['Coin'] for r in results]
669
  n = len(coins_list)
670
  spread_matrix = np.random.uniform(0.01, 0.5, (n, n))
671
  np.fill_diagonal(spread_matrix, 0)
672
-
673
- fig = go.Figure(data=go.Heatmap(z=spread_matrix*100, x=coins_list, y=coins_list,
674
- colorscale='RdYlGn_r', text=np.round(spread_matrix*100,2), texttemplate='%{text:.2f}%',
675
- colorbar=dict(title='Arb Spread %')))
676
- fig.update_layout(title='Cross-Exchange Arbitrage Spread Heatmap (Simulated)',
677
- template='plotly_dark', height=450, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
678
-
679
  data_note = f"\n\n> {synthetic_note}\n" if synthetic_note else ""
680
-
681
- md = f"""## 🪙 Crypto Arbitrage Scanner{data_note}
682
-
683
- {df.to_markdown(index=False)}
684
-
685
- ### Jane Street Level:
686
- - **Cross-exchange latency arb** — requires sub-millisecond co-location (Jump Trading)
687
- - **Triangular arb** — BTC→ETH→USDT→BTC loop exploiting pricing inefficiencies
688
- - **Funding rate arb** — perpetual vs spot basis trade (annualized 8-40% yield)
689
- - **Regime dependency** — arb spreads collapse during high volatility (GARCH effect)
690
- """
691
  return fig, md
692
 
693
- # =============================================================================
694
- # RISK ENGINE + STRESS TEST
695
- # =============================================================================
696
- def risk_engine(tickers, stress_spot):
697
  ts = [t.strip().upper() for t in tickers.split(',') if t.strip()]
698
  data = {}
699
  synthetic_note = ""
700
  for t in ts:
701
- df, info, _ = fetch(t, "1y")
702
  if df is not None and len(df) > 30:
703
  data[t] = df['Close']
704
  if info and 'note' in info:
@@ -707,17 +465,17 @@ def risk_engine(tickers, stress_spot):
707
  return None, None, "Need at least 2 tickers."
708
  prices = pd.DataFrame(data).dropna()
709
  rets = prices.pct_change().dropna()
710
-
711
  w = np.ones(len(data))/len(data)
712
  cov = rets.cov()*252
713
  mu = rets.mean()*252
714
-
715
  port_ret = np.dot(w, mu)
716
  port_vol = np.sqrt(np.dot(w.T, np.dot(cov, w)))
717
-
718
  var_95 = np.percentile(np.dot(rets, w), 5)
719
  var_99 = np.percentile(np.dot(rets, w), 1)
720
-
 
 
 
721
  stress_rets = rets.copy()
722
  for col in stress_rets.columns:
723
  if stress_spot.get(col, 0) != 0:
@@ -725,56 +483,28 @@ def risk_engine(tickers, stress_spot):
725
  stress_port = np.dot(stress_rets, w)
726
  stress_var95 = np.percentile(stress_port, 5)
727
  stress_var99 = np.percentile(stress_port, 1)
728
-
729
  corr = rets.corr()
730
- fig1 = go.Figure(data=go.Heatmap(z=corr.values, x=corr.columns, y=corr.columns,
731
- colorscale='RdBu', zmid=0, text=np.round(corr.values,2), texttemplate='%{text:.2f}',
732
- colorbar=dict(title='Correlation')))
733
- fig1.update_layout(title='Asset Correlation Matrix', template='plotly_dark',
734
- height=450, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
735
-
736
  fig2 = go.Figure()
737
  fig2.add_trace(go.Histogram(x=np.dot(rets, w)*100, nbinsx=50, marker_color='#FF6B00', opacity=0.7, name='Normal'))
738
  fig2.add_trace(go.Histogram(x=stress_port*100, nbinsx=50, marker_color='#FF5252', opacity=0.5, name='Stressed'))
739
- fig2.add_vline(x=var_95*100, line_color='#00C853', line_dash='dash', annotation_text=f'VaR95')
740
- fig2.add_vline(x=stress_var95*100, line_color='#FF5252', line_dash='dash', annotation_text=f'Stress VaR95')
741
- fig2.update_layout(title='Portfolio Return Distribution: Normal vs Stressed',
742
- template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
743
-
744
  data_note = f"\n\n> {synthetic_note}\n" if synthetic_note else ""
745
-
746
- md = f"""## 🛡️ Algorithmic Risk Engine{data_note}
747
-
748
- | Metric | Normal | Stressed |
749
- |--------|--------|----------|
750
- | Expected Return | {port_ret*100:.1f}% | — |
751
- | Volatility | {port_vol*100:.1f}% | — |
752
- | Sharpe | {port_ret/(port_vol+1e-10):.2f} | — |
753
- | VaR (95%) | {var_95*100:.2f}% | {stress_var95*100:.2f}% |
754
- | VaR (99%) | {var_99*100:.2f}% | {stress_var99*100:.2f}% |
755
-
756
- ### Jane Street Level:
757
- - **Parametric + Historical VaR** — dual methodology for regulatory compliance
758
- - **Stress testing** — shocks from 2008, 2020 COVID, 2022 rate hikes
759
- - **Correlation breakdown** — during crisis, correlations → 1 (diversification fails)
760
- - **Tail risk** — Student-t distribution better than normal for fat tails
761
- """
762
  return fig1, fig2, md
763
 
764
- # =============================================================================
765
- # SENTIMENT ANALYZER
766
- # =============================================================================
767
  def sentiment_analyzer(ticker):
768
- df, info, err = fetch(ticker, "3mo")
769
- if df is None:
770
- return None, f"Error: {err}"
771
  df = add_indicators(df)
772
-
773
  rsi_sent = 'Bullish' if df['RSI'].iloc[-1] > 55 else 'Bearish' if df['RSI'].iloc[-1] < 45 else 'Neutral'
774
  macd_sent = 'Bullish' if df['MACD'].iloc[-1] > df['MACDS'].iloc[-1] else 'Bearish'
775
  vol_sent = 'High Interest' if df['VR'].iloc[-1] > 1.5 else 'Normal'
776
  trend_sent = 'Uptrend' if df['Close'].iloc[-1] > df['SMA20'].iloc[-1] > df['SMA50'].iloc[-1] else 'Downtrend' if df['Close'].iloc[-1] < df['SMA20'].iloc[-1] < df['SMA50'].iloc[-1] else 'Mixed'
777
-
778
  keywords = []
779
  if info:
780
  sector = info.get('sector', '')
@@ -785,120 +515,66 @@ def sentiment_analyzer(ticker):
785
  else: keywords = ['Earnings', 'Guidance', 'Macro', 'Inflation', 'Fed']
786
  else:
787
  keywords = ['Earnings', 'Guidance', 'Macro', 'Inflation', 'Fed']
788
-
789
  score = 0
790
  score += 20 if rsi_sent == 'Bullish' else -20 if rsi_sent == 'Bearish' else 0
791
  score += 15 if macd_sent == 'Bullish' else -15
792
  score += 10 if trend_sent == 'Uptrend' else -10 if trend_sent == 'Downtrend' else 0
793
  score += 10 if vol_sent == 'High Interest' else 0
794
  score = max(-100, min(100, score))
795
-
796
  fig = go.Figure()
797
- fig.add_trace(go.Indicator(mode="gauge+number+delta", value=score,
798
- domain={'x': [0, 1], 'y': [0, 1]},
799
  title={'text': f"{ticker} Sentiment Score", 'font': {'size': 24, 'color': '#e6edf3'}},
800
  delta={'reference': 0, 'increasing': {'color': '#00C853'}, 'decreasing': {'color': '#FF5252'}},
801
- gauge={'axis': {'range': [-100, 100], 'tickcolor': '#e6edf3'},
802
- 'bar': {'color': '#FF6B00'},
803
- 'bgcolor': '#0a0a0a',
804
- 'borderwidth': 2,
805
- 'bordercolor': '#30363d',
806
- 'steps': [
807
- {'range': [-100, -50], 'color': 'rgba(255,82,82,0.3)'},
808
- {'range': [-50, 0], 'color': 'rgba(255,107,0,0.2)'},
809
- {'range': [0, 50], 'color': 'rgba(0,212,255,0.2)'},
810
- {'range': [50, 100], 'color': 'rgba(0,200,83,0.3)'}],
811
  'threshold': {'line': {'color': 'white', 'width': 4}, 'thickness': 0.75, 'value': score}}))
812
  fig.update_layout(template='plotly_dark', height=450, paper_bgcolor='#000000', font=dict(color='#e6edf3'))
813
-
814
- kdf = pd.DataFrame({'Keyword': keywords, 'Sentiment': ['Bullish','Neutral','Bullish','Bearish','Neutral'][:len(keywords)],
815
- 'Weight': [0.3,0.2,0.25,0.15,0.1][:len(keywords)]})
816
-
817
- data_note = ""
818
- if info and 'note' in info:
819
- data_note = f"\n\n> {info['note']}\n"
820
-
821
- md = f"""## 📰 Earnings Call Sentiment Analyzer{data_note}
822
-
823
- | Signal | Value |
824
- |--------|-------|
825
- | RSI Sentiment | {rsi_sent} |
826
- | MACD Sentiment | {macd_sent} |
827
- | Volume Sentiment | {vol_sent} |
828
- | Trend Sentiment | {trend_sent} |
829
- | **Composite Score** | **{score}/100** |
830
-
831
- ### Keywords Detected
832
- {kdf.to_markdown(index=False)}
833
-
834
- ### Jane Street Level:
835
- - **Multi-source NLP pipeline** — Bloomberg headlines, SEC filings, Twitter, Reddit
836
- - **Named Entity Recognition** — identifies company mentions, executive names, product launches
837
- - **Temporal analysis** — sentiment momentum (improving vs deteriorating)
838
- - **Alpha factor** — sentiment surprise (actual vs consensus) → 0.3-0.5 IC
839
- """
840
  return fig, md
841
 
842
- # =============================================================================
843
- # MACRO ANALYSIS
844
- # =============================================================================
845
  def macro_analysis():
846
  macros = {}
847
  synthetic_note = ""
848
  for t, name in [('^GSPC','S&P 500'),('^IXIC','Nasdaq'),('^TNX','10Y Treasury'),('GC=F','Gold'),('CL=F','Oil'),('EURUSD=X','EUR/USD'),('DX-Y.NYB','DXY Dollar'),('BTC-USD','Bitcoin')]:
849
- df, info, err = fetch(t, "3mo")
850
  if df is not None and not df.empty:
851
- macros[name] = {'price': df['Close'].iloc[-1], '1m': (df['Close'].iloc[-1]/df['Close'].iloc[0]-1)*100,
852
- '3m': (df['Close'].iloc[-1]/df['Close'].iloc[max(0,len(df)-63)]-1)*100 if len(df)>63 else 0}
853
  if info and 'note' in info:
854
  synthetic_note = info['note']
855
-
856
  if not macros:
857
  return None, "Could not fetch macro data."
858
-
859
  fig = go.Figure()
860
  names = list(macros.keys())
861
  vals = [macros[n]['1m'] for n in names]
862
  colors = ['#00C853' if v>0 else '#FF5252' for v in vals]
863
  fig.add_trace(go.Bar(x=names, y=vals, marker_color=colors, name='1M Change'))
864
- fig.update_layout(title='Cross-Asset Performance (1 Month)', template='plotly_dark',
865
- yaxis_title='% Change', height=450, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
866
-
867
- md = "## 🌍 Global Macro Dashboard\n\n| Asset | Price | 1M Change | 3M Change |\n|-------|-------|-----------|-----------|\n"
868
  for n in names:
869
  md += f"| {n} | ${macros[n]['price']:.2f} | {macros[n]['1m']:+.1f}% | {macros[n]['3m']:+.1f}% |\n"
870
-
871
  if synthetic_note:
872
  md += f"\n> {synthetic_note}\n"
873
-
874
- md += """\n### Jane Street Level:
875
- - **Growth/Inflation quadrant** — determines asset allocation (Bridgewater All Weather)
876
- - **Dollar regime** — DXY > 100 = risk-off, emerging market stress
877
- - **Rate curve shape** — 10Y-2Y spread inversion = recession signal (9/10 accuracy)
878
- - **Cross-asset momentum** — trend-following on macro factors (Asness value/momentum)
879
- """
880
  return fig, md
881
 
882
- # =============================================================================
883
- # TECHNICAL ANALYSIS (FULL DASHBOARD)
884
- # =============================================================================
885
  def tech_analysis(ticker, market, period):
886
  suffix = MARKETS.get(market, {}).get('suffix', '')
887
  if suffix and not any(ticker.endswith(s) for s in suffix.split('|')):
888
  ticker = ticker + suffix
889
- df, info, err = fetch(ticker, period)
890
- if df is None:
891
- return [None]*6 + [f"Error: {err}"]
892
  df = add_indicators(df)
893
  rk = risk_metrics(df['Ret'])
894
  if not rk:
895
  return [None]*6 + ["Need more data."]
896
  l = df.iloc[-1]
897
-
898
- fig1 = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.03,
899
- row_heights=[0.55, 0.25, 0.20], subplot_titles=(ticker, 'Volume', 'RSI'))
900
- fig1.add_trace(go.Candlestick(x=df.index, open=df['Open'], high=df['High'], low=df['Low'], close=df['Close'],
901
- increasing_line_color='#00C853', decreasing_line_color='#FF5252'), row=1, col=1)
902
  for c,w in [('SMA20','#FF6B00'),('SMA50','#00D4FF'),('SMA200','#9C27B0')]:
903
  fig1.add_trace(go.Scatter(x=df.index, y=df[c], line=dict(color=w, width=1), name=c), row=1, col=1)
904
  fig1.add_trace(go.Scatter(x=df.index, y=df['BBU'], line=dict(color='gray', width=0.8, dash='dash'), opacity=0.4), row=1, col=1)
@@ -908,85 +584,45 @@ def tech_analysis(ticker, market, period):
908
  fig1.add_trace(go.Scatter(x=df.index, y=df['RSI'], line=dict(color='#9C27B0', width=1.5), fill='tozeroy'), row=3, col=1)
909
  fig1.add_hline(y=70, line_dash="dash", line_color="#FF5252", row=3, col=1)
910
  fig1.add_hline(y=30, line_dash="dash", line_color="#00C853", row=3, col=1)
911
- fig1.update_layout(title=f'{ticker} Technical Dashboard', template='plotly_dark', height=900,
912
- paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
913
-
914
  fig2 = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, row_heights=[0.6,0.4])
915
  fig2.add_trace(go.Scatter(x=df.index, y=df['MACD'], line=dict(color='#00D4FF', width=1.5), name='MACD'), row=1, col=1)
916
  fig2.add_trace(go.Scatter(x=df.index, y=df['MACDS'], line=dict(color='#FF6B00', width=1.5), name='Signal'), row=1, col=1)
917
  fig2.add_trace(go.Bar(x=df.index, y=df['MACDH'], marker_color=['#00C853' if v>=0 else '#FF5252' for v in df['MACDH']], opacity=0.6), row=2, col=1)
918
  fig2.update_layout(title='MACD', template='plotly_dark', height=450, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
919
-
920
  fig3 = go.Figure()
921
  fig3.add_trace(go.Scatter(x=df.index, y=df['pDI'], line=dict(color='#00C853', width=1), name='+DI'))
922
  fig3.add_trace(go.Scatter(x=df.index, y=df['mDI'], line=dict(color='#FF5252', width=1), name='-DI'))
923
  fig3.add_trace(go.Scatter(x=df.index, y=df['ADX'], line=dict(color='#00D4FF', width=2), name='ADX'))
924
  fig3.add_hline(y=25, line_dash="dash", line_color="gray")
925
  fig3.update_layout(title='ADX Trend Strength', template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
926
-
927
  fig4 = go.Figure()
928
  fig4.add_trace(go.Histogram(x=df['Ret'].dropna()*100, nbinsx=50, marker_color='#FF6B00', opacity=0.7))
929
  fig4.add_vline(x=rk['v95']*100, line_color='#FF5252', line_dash='dash', annotation_text='VaR95')
930
  fig4.add_vline(x=df['Ret'].mean()*100, line_color='#00C853', line_dash='dash')
931
  fig4.update_layout(title='Return Distribution', template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
932
-
933
  fig5 = go.Figure()
934
  fig5.add_trace(go.Scatter(x=df.index, y=df['ATR_pct'], line=dict(color='#FF6B00', width=1.5), fill='tozeroy'))
935
  fig5.update_layout(title='ATR % (Volatility)', template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
936
-
937
  fig6 = go.Figure()
938
  fig6.add_trace(go.Scatter(x=df.index, y=df['ICH_SA'], line=dict(color='#00C853', width=0.5), name='Senkou A'))
939
  fig6.add_trace(go.Scatter(x=df.index, y=df['ICH_SB'], fill='tonexty', fillcolor='rgba(0,200,83,0.1)', line=dict(color='#FF5252', width=0.5), name='Senkou B'))
940
  fig6.add_trace(go.Scatter(x=df.index, y=df['Close'], line=dict(color='#00D4FF', width=1.5), name='Price'))
941
  fig6.update_layout(title='Ichimoku Cloud', template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
942
-
943
- data_note = ""
944
- if info and 'note' in info:
945
- data_note = f"\n\n> {info['note']}\n"
946
-
947
- md = f"""## 📈 {ticker} Technical Analysis{data_note}
948
-
949
- | Metric | Value |
950
- |--------|-------|
951
- | Price | ${l['Close']:.2f} |
952
- | RSI | {l['RSI']:.1f} |
953
- | MACD | {l['MACD']:.3f} |
954
- | ADX | {l['ADX']:.1f} |
955
- | ATR % | {l['ATR_pct']:.2f}% |
956
- | Volume Ratio | {l['VR']:.1f}x |
957
-
958
- ### Risk Metrics
959
- | Metric | Value |
960
- |--------|-------|
961
- | Ann Return | {rk['ar']*100:.1f}% |
962
- | Ann Vol | {rk['av']*100:.1f}% |
963
- | Sharpe | {rk['sh']:.2f} |
964
- | Max DD | {rk['md']*100:.1f}% |
965
- | VaR95 | {rk['v95']*100:.2f}% |
966
- | Win Rate | {rk['wr']*100:.1f}% |
967
-
968
- ### Jane Street Level:
969
- - **18+ indicators** — same toolkit used by systematic trading desks
970
- - **Ichimoku Cloud** — Japanese institutional benchmark for trend/momentum
971
- - **ADX regime detection** — <20 = range-bound, >40 = strong trend (filter false breakouts)
972
- - **ATR position sizing** — Kelly criterion adaptation for optimal capital allocation
973
- """
974
  return [fig1, fig2, fig3, fig4, fig5, fig6, md]
975
 
976
- # =============================================================================
977
- # AI ANALYSIS (K2 THINK V2)
978
- # =============================================================================
979
  def ai_analysis(ticker, market, period):
980
  suffix = MARKETS.get(market, {}).get('suffix', '')
981
  if suffix and not any(ticker.endswith(s) for s in suffix.split('|')):
982
  ticker = ticker + suffix
983
- df, info, err = fetch(ticker, period)
984
- if df is None:
985
- return f"Error: {err}"
986
  df = add_indicators(df)
987
  rk = risk_metrics(df['Ret'])
988
  l = df.iloc[-1]
989
-
990
  prompt = f"""You are a portfolio manager at Jane Street / Two Sigma managing $5B AUM.
991
 
992
  TICKER: {ticker}
@@ -1009,267 +645,233 @@ Provide:
1009
  7. CONTRARIAN VIEW (what would make this wrong)
1010
 
1011
  Use quantitative reasoning. Reference specific numbers."""
1012
-
1013
  client = K2ThinkClient()
1014
  return client.chat([{"role":"user","content":prompt}], temperature=0.2, max_tokens=4096)
1015
 
1016
- # =============================================================================
1017
- # GRADIO APP - BLOOMBERG TERMINAL AESTHETIC
1018
- # =============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1019
  def build_app():
1020
  with gr.Blocks(
1021
  title="AlphaForge V3.1 - Institutional Quant Platform",
1022
  theme=gr.themes.Soft(primary_hue="orange", secondary_hue="cyan", neutral_hue="gray",
1023
  font=[gr.themes.GoogleFont("Roboto Mono"), "monospace"]),
1024
- css="""
1025
- body { background: #000000 !important; }
1026
- .gradio-container { background: #000000 !important; color: #e6edf3 !important; }
1027
- .tabitem { background: #0a0a0a !important; border: 1px solid #1a1a1a !important; border-radius: 8px !important; }
1028
- .tab-nav { background: #000000 !important; border-bottom: 2px solid #FF6B00 !important; }
1029
- .tab-nav button { color: #888 !important; background: transparent !important; font-family: 'Roboto Mono', monospace !important; font-size: 0.85em !important; }
1030
- .tab-nav button.selected { color: #FF6B00 !important; border-bottom: 2px solid #FF6B00 !important; font-weight: bold !important; }
1031
- input, textarea, select { background: #111 !important; color: #00D4FF !important; border: 1px solid #333 !important; font-family: 'Roboto Mono', monospace !important; }
1032
- button.primary { background: #FF6B00 !important; color: #000 !important; font-weight: 700 !important; font-family: 'Roboto Mono', monospace !important; border-radius: 4px !important; }
1033
- button.secondary { background: #1a1a1a !important; color: #FF6B00 !important; border: 1px solid #FF6B00 !important; font-family: 'Roboto Mono', monospace !important; }
1034
- .markdown-body { color: #e6edf3 !important; font-family: 'Roboto Mono', monospace !important; }
1035
- .markdown-body h1 { color: #FF6B00 !important; border-bottom: 1px solid #333 !important; font-size: 1.3em !important; }
1036
- .markdown-body h2 { color: #00D4FF !important; font-size: 1.1em !important; }
1037
- .markdown-body h3 { color: #00C853 !important; font-size: 1em !important; }
1038
- .markdown-body table { border-color: #333 !important; font-size: 0.85em !important; }
1039
- .markdown-body th { background: #111 !important; color: #FF6B00 !important; }
1040
- .markdown-body td { border-color: #333 !important; }
1041
- .title-bar { text-align: center; padding: 20px 0; border-bottom: 2px solid #FF6B00; }
1042
- .title-bar h1 { font-size: 2.5em; font-weight: 800; margin: 0; color: #FF6B00; font-family: 'Roboto Mono', monospace !important; letter-spacing: -1px; }
1043
- .title-bar p { color: #888; font-size: 0.9em; margin-top: 4px; font-family: 'Roboto Mono', monospace !important; }
1044
- .badge-row { text-align: center; margin: 12px 0 20px; }
1045
- .badge { display: inline-block; padding: 4px 12px; margin: 3px; border-radius: 2px; font-size: 0.75em; font-weight: 600; font-family: 'Roboto Mono', monospace !important; }
1046
- .badge-api { background: #FF6B00; color: #000; }
1047
- .badge-data { background: #00C853; color: #000; }
1048
- .badge-alpha { background: #00D4FF; color: #000; }
1049
- .k2-status { text-align: center; padding: 6px; margin: 6px 0; border: 1px solid; font-size: 0.8em; font-family: 'Roboto Mono', monospace !important; }
1050
- .k2-ok { color: #00C853; border-color: #00C853; background: rgba(0,200,83,0.1); }
1051
- .k2-err { color: #FF5252; border-color: #FF5252; background: rgba(255,82,82,0.1); }
1052
- """
1053
- ) as demo:
1054
- # HEADER
1055
- gr.HTML("""
1056
- <div class="title-bar">
1057
- <h1>▲ ALPHAFORGE V3.1</h1>
1058
- <p>INSTITUTIONAL QUANTITATIVE TRADING PLATFORM // JANE STREET // TWO SIGMA // CITADEL LEVEL</p>
1059
- </div>
1060
- <div class="badge-row">
1061
- <span class="badge badge-api">K2 THINK V2 AI</span>
1062
- <span class="badge badge-data">MULTI-MARKET</span>
1063
- <span class="badge badge-alpha">ALPHA ENGINE</span>
1064
- <span class="badge badge-api">OPTIONS</span>
1065
- <span class="badge badge-data">PAIRS</span>
1066
- <span class="badge badge-alpha">CRYPTO ARB</span>
1067
- <span class="badge badge-api">RISK ENGINE</span>
1068
- <span class="badge badge-data">SENTIMENT</span>
1069
  </div>
1070
  """)
1071
-
1072
- k2_cls = "k2-ok" if K2_API_KEY else "k2-err"
1073
- k2_txt = f"[{'CONNECTED' if K2_API_KEY else 'OFFLINE'}] K2_THINK_V2_API // MBZUAI // {'KEY_VALID' if K2_API_KEY else 'ADD_K2_API_KEY_SECRET'}"
1074
- gr.HTML(f'<div class="k2-status {k2_cls}">{k2_txt}</div>')
1075
-
1076
- # ── TAB 1: TECHNICAL ANALYSIS ──
1077
- with gr.Tab("📈 TECHNICAL"):
1078
- with gr.Row():
1079
- with gr.Column(scale=1):
1080
- mkt = gr.Dropdown(label="MARKET", choices=list(MARKETS.keys()), value="US Equities")
1081
- sym = gr.Textbox(label="TICKER", value="AAPL")
1082
- per = gr.Dropdown(label="PERIOD", choices=["1mo","3mo","6mo","1y","2y","5y"], value="1y")
1083
- gr.Button("ANALYZE", variant="primary").click(
1084
- fn=tech_analysis, inputs=[sym, mkt, per],
1085
- outputs=[gr.Plot(), gr.Plot(), gr.Plot(), gr.Plot(), gr.Plot(), gr.Plot(), gr.Markdown()])
1086
- gr.Button("K2 AI ANALYSIS", variant="secondary").click(
1087
- fn=ai_analysis, inputs=[sym, mkt, per],
1088
- outputs=[gr.Textbox(label="K2 THINK V2 ANALYSIS", lines=30)])
1089
- with gr.Column(scale=2):
1090
- gr.Markdown()
1091
-
1092
- # ── TAB 2: STRATEGY BACKTESTER ──
1093
- with gr.Tab(" BACKTEST"):
1094
- with gr.Row():
1095
- with gr.Column(scale=1):
1096
- bt_sym = gr.Textbox(label="TICKER", value="AAPL")
1097
- bt_strat = gr.Dropdown(label="STRATEGY", choices=["Moving Average Crossover","RSI Strategy","MACD Momentum","Mean Reversion","Bollinger Squeeze"], value="Moving Average Crossover")
1098
- bt_cap = gr.Number(label="START CAPITAL", value=100000)
1099
- bt_risk = gr.Slider(label="RISK % PER TRADE", minimum=1, maximum=50, value=10)
1100
- bt_per = gr.Dropdown(label="PERIOD", choices=["1y","2y","5y"], value="2y")
1101
- gr.Button("RUN BACKTEST", variant="primary").click(
1102
- fn=backtest, inputs=[bt_sym, bt_strat, bt_cap, bt_risk, bt_per],
1103
- outputs=[gr.Plot(label="EQUITY CURVE"), gr.Plot(label="DRAWDOWN"), gr.Dataframe(label="TRADE LOG"), gr.Markdown(), gr.Textbox()])
1104
-
1105
- # ── TAB 3: PORTFOLIO OPTIMIZER ──
1106
- with gr.Tab("💼 PORTFOLIO"):
1107
- with gr.Row():
1108
- with gr.Column(scale=1):
1109
- port_tickers = gr.Textbox(label="TICKERS (COMMA-SEPARATED)", value="AAPL, MSFT, GOOGL, AMZN, NVDA")
1110
- port_per = gr.Dropdown(label="LOOKBACK", choices=["6mo","1y","2y"], value="1y")
1111
- gr.Button("OPTIMIZE (MPT)", variant="primary").click(
1112
- fn=optimize_portfolio, inputs=[port_tickers, port_per],
1113
- outputs=[gr.Plot(label="EFFICIENT FRONTIER"), gr.Plot(label="ALLOCATION"), gr.Dataframe(label="WEIGHTS"), gr.Markdown()])
1114
-
1115
- # ── TAB 4: OPTIONS PRICING ──
1116
- with gr.Tab("📐 OPTIONS"):
1117
- with gr.Row():
1118
- with gr.Column(scale=1):
1119
- opt_sym = gr.Textbox(label="UNDERLYING", value="AAPL")
1120
- opt_type = gr.Dropdown(label="TYPE", choices=["Call","Put"], value="Call")
1121
- opt_strike = gr.Slider(label="STRIKE % SPOT", minimum=70, maximum=130, value=100)
1122
- opt_days = gr.Slider(label="DAYS TO EXPIRY", minimum=7, maximum=365, value=30)
1123
- opt_rfr = gr.Slider(label="RISK-FREE %", minimum=0, maximum=10, value=4.5)
1124
- opt_vol = gr.Number(label="VOL OVERRIDE % (0=HIST)", value=0)
1125
- gr.Button("PRICE (BLACK-SCHOLES)", variant="primary").click(
1126
- fn=options_pricing, inputs=[opt_sym, opt_strike, opt_days, opt_rfr, opt_vol, opt_type],
1127
- outputs=[gr.Plot(label="GREEKS"), gr.Dataframe(label="P/L SCENARIOS"), gr.Markdown()])
1128
-
1129
- # ── TAB 5: PAIRS TRADING ──
1130
- with gr.Tab("🔗 PAIRS"):
1131
- with gr.Row():
1132
- with gr.Column(scale=1):
1133
- pair_a = gr.Textbox(label="TICKER A (LONG)", value="AAPL")
1134
- pair_b = gr.Textbox(label="TICKER B (SHORT)", value="MSFT")
1135
- pair_per = gr.Dropdown(label="LOOKBACK", choices=["6mo","1y","2y"], value="1y")
1136
- gr.Button("ANALYZE PAIR", variant="primary").click(
1137
- fn=pairs_trade, inputs=[pair_a, pair_b, pair_per],
1138
- outputs=[gr.Plot(label="SPREAD ANALYSIS"), gr.Plot(label="PRICE RELATIONSHIP"), gr.Markdown()])
1139
-
1140
- # ── TAB 6: CRYPTO ARBITRAGE ──
1141
- with gr.Tab("🪙 CRYPTO ARB"):
1142
- with gr.Row():
1143
- with gr.Column(scale=1):
1144
- crypto_input = gr.Textbox(label="COINS (COMMA-SEPARATED)", value="BTC, ETH, SOL, XRP, ADA")
1145
- gr.Button("SCAN ARBITRAGE", variant="primary").click(
1146
- fn=crypto_arbitrage, inputs=[crypto_input],
1147
- outputs=[gr.Plot(label="ARBITRAGE HEATMAP"), gr.Markdown()])
1148
-
1149
- # ── TAB 7: RISK ENGINE ──
1150
- with gr.Tab("🛡️ RISK"):
1151
- with gr.Row():
1152
- with gr.Column(scale=1):
1153
- risk_tickers = gr.Textbox(label="PORTFOLIO TICKERS", value="AAPL, MSFT, GOOGL, TSLA, JPM")
1154
- gr.Markdown("### STRESS TEST SHOCKS (%)")
1155
- stress_aapl = gr.Slider(label="AAPL SHOCK", minimum=-50, maximum=50, value=0)
1156
- stress_tsla = gr.Slider(label="TSLA SHOCK", minimum=-50, maximum=50, value=0)
1157
- stress_spy = gr.Slider(label="SPY SHOCK", minimum=-50, maximum=50, value=-20)
1158
- def risk_wrapper(tickers, a, t, s):
1159
- shocks = {'AAPL': a, 'TSLA': t, 'SPY': s}
1160
- return risk_engine(tickers, shocks)
1161
- gr.Button("RUN STRESS TEST", variant="primary").click(
1162
- fn=risk_wrapper, inputs=[risk_tickers, stress_aapl, stress_tsla, stress_spy],
1163
- outputs=[gr.Plot(label="CORRELATION MATRIX"), gr.Plot(label="DISTRIBUTION"), gr.Markdown()])
1164
-
1165
- # ── TAB 8: SENTIMENT ──
1166
- with gr.Tab("📰 SENTIMENT"):
1167
- with gr.Row():
1168
- with gr.Column(scale=1):
1169
- sent_sym = gr.Textbox(label="TICKER", value="TSLA")
1170
- gr.Button("ANALYZE SENTIMENT", variant="primary").click(
1171
- fn=sentiment_analyzer, inputs=[sent_sym],
1172
- outputs=[gr.Plot(label="SENTIMENT GAUGE"), gr.Markdown()])
1173
-
1174
- # ── TAB 9: MACRO ──
1175
- with gr.Tab("🌍 MACRO"):
1176
- gr.Button("REFRESH MACRO DASHBOARD", variant="primary").click(
1177
- fn=macro_analysis, outputs=[gr.Plot(label="CROSS-ASSET"), gr.Markdown()])
1178
-
1179
- # ── TAB 10: ABOUT ──
1180
- with gr.Tab("ℹ️ ABOUT"):
1181
- gr.Markdown("""
1182
- ## ALPHAFORGE V3.1 WHY THIS IS JANE STREET LEVEL
1183
-
1184
- ### 1. STRATEGY BACKTESTER
1185
- | Feature | Jane Street Practice |
1186
- |---------|---------------------|
1187
- | ATR position sizing | Adaptive to vol regime (not fixed shares) |
1188
- | Signal confirmation | Requires dual-indicator convergence |
1189
- | Time-based exits | Prevents trap trades in choppy markets |
1190
- | Slippage modeling | 0.5x sizing = institutional market impact |
1191
-
1192
- ### 2. PORTFOLIO OPTIMIZER (Markowitz MPT)
1193
- | Feature | Jane Street Practice |
1194
- |---------|---------------------|
1195
- | 10,000 portfolio MC | Same scale as AQR, D.E. Shaw |
1196
- | 50% concentration limit | Regulatory/ risk control standard |
1197
- | Sharpe maximization | Objective function at Renaissance Technologies |
1198
- | Mean-variance framework | Harry Markowitz 1952 Nobel Prize |
1199
-
1200
- ### 3. OPTIONS PRICING (Black-Scholes)
1201
- | Feature | Jane Street Practice |
1202
- |---------|---------------------|
1203
- | Analytic Greeks | Exact derivatives (not finite differences) |
1204
- | Scenario analysis | P/L at +/-30% spot = stress testing |
1205
- | Gamma convexity | Essential for delta-hedging desks |
1206
- | SciPy precision | Institutional-grade numerical methods |
1207
-
1208
- ### 4. PAIRS TRADING
1209
- | Feature | Jane Street Practice |
1210
- |---------|---------------------|
1211
- | OU half-life | Jarrow-Whisnaugh mean-reversion speed |
1212
- | OLS hedge ratio | Engle-Granger cointegration |
1213
- | Z-score thresholds | +/-2σ entry, 0 exit (stat arb standard) |
1214
- | Capacity estimate | Half-life <20d = tradeable |
1215
-
1216
- ### 5. CRYPTO ARBITRAGE
1217
- | Feature | Jane Street Practice |
1218
- |---------|---------------------|
1219
- | Cross-exchange latency | Requires sub-millisecond co-location |
1220
- | Triangular arb | BTC->ETH->USDT loop exploitation |
1221
- | Funding rate arb | Perpetual vs spot basis (8-40% annualized) |
1222
- | GARCH regime | Spreads collapse in high volatility |
1223
-
1224
- ### 6. RISK ENGINE
1225
- | Feature | Jane Street Practice |
1226
- |---------|---------------------|
1227
- | Parametric + Historical VaR | Dual methodology for compliance |
1228
- | Stress testing | 2008, COVID-2020, 2022 rate hike scenarios |
1229
- | Correlation breakdown | Crisis: correlations -> 1 |
1230
- | Student-t tails | Fat-tail distribution modeling |
1231
-
1232
- ### 7. SENTIMENT ANALYZER
1233
- | Feature | Jane Street Practice |
1234
- |---------|---------------------|
1235
- | Multi-source NLP | Bloomberg, SEC filings, Twitter, Reddit |
1236
- | Named Entity Recognition | Company/executive/product extraction |
1237
- | Temporal momentum | Improving vs deteriorating sentiment |
1238
- | Alpha factor | Sentiment surprise IC = 0.3-0.5 |
1239
-
1240
- ### 8. MACRO DASHBOARD
1241
- | Feature | Jane Street Practice |
1242
- |---------|---------------------|
1243
- | Growth/Inflation quadrant | Bridgewater All Weather framework |
1244
- | Dollar regime | DXY > 100 = risk-off, EM stress |
1245
- | Yield curve | 10Y-2Y inversion = recession (9/10) |
1246
- | Cross-asset momentum | Asness value/momentum factors |
1247
-
1248
- ### Data Sources
1249
- - **Primary**: yfinance (Yahoo Finance) for real market data
1250
- - **Fallback**: Deterministic synthetic data engine when Yahoo rate-limits
1251
- - **Consistency**: Same ticker = same data on same day (seeded generation)
1252
-
1253
- ### Setup
1254
- - K2 Think V2 API key: Add `K2_API_KEY` in Space Settings > Repository Secrets
1255
- - No key required: All quant modules work standalone
1256
-
1257
- ### Stack
1258
- - yfinance / synthetic fallback (market data)
1259
- - Plotly (Bloomberg Terminal aesthetic)
1260
- - NumPy/Pandas (vectorized quant math)
1261
- - K2 Think V2 (MBZUAI reasoning)
1262
-
1263
- ### Links
1264
- - [Full AlphaForge](https://huggingface.co/Premchan369/alphaforge-quant-system)
1265
- - [Build with K2](https://build.k2think.ai/)
1266
- - [MBZUAI](https://mbzuai.ac.ae/)
1267
-
1268
- *Built by Premchan | Build with K2 Think V2*
1269
- """)
1270
-
1271
- return demo
1272
 
1273
  if __name__ == "__main__":
1274
- demo = build_app()
1275
- demo.queue().launch(server_name="0.0.0.0", server_port=7860)
 
1
+ """AlphaForge V3.1 - Institutional Quant Trading Platform"""
 
 
 
 
 
 
 
 
2
  import os, json, warnings, math, random, time, hashlib, threading
3
+ from datetime import datetime
4
  warnings.filterwarnings('ignore')
5
+ import gradio as gr
6
+ import requests
7
+ import yfinance as yf
8
+ import pandas as pd
9
+ import numpy as np
10
+ import plotly.graph_objects as go
11
+ from plotly.subplots import make_subplots
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  K2_API_KEY = os.environ.get("K2_API_KEY", "")
14
  K2_BASE_URL = "https://api.k2think.ai/v1/chat/completions"
15
  K2_MODEL = "MBZUAI-IFM/K2-Think-v2"
16
 
 
 
 
17
  class K2ThinkClient:
18
  def __init__(self):
19
  self.api_key = K2_API_KEY
20
  self.available = bool(self.api_key) and len(self.api_key) > 10
 
21
  def chat(self, messages, temperature=0.3, max_tokens=4096):
22
  if not self.available:
23
+ return "K2 Think V2 API Not Configured. Add K2_API_KEY in Space Settings > Repository Secrets. All quant features work without it!"
24
  payload = {"model": K2_MODEL, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, "stream": False}
25
  headers = {"accept": "application/json", "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
26
  try:
 
29
  j = r.json()
30
  return j['choices'][0]['message']['content'] if 'choices' in j and j['choices'] else str(j)[:400]
31
  except requests.exceptions.Timeout:
32
+ return "Timeout. API under high load."
33
  except requests.exceptions.HTTPError as e:
34
+ return f"Auth/Rate Error ({e.response.status_code})" if e.response else str(e)[:200]
35
  except Exception as e:
36
+ return f"Error: {str(e)[:300]}"
37
 
 
 
 
38
  def _ticker_seed(ticker):
 
 
39
  d = datetime.utcnow().strftime("%Y%m%d")
40
  return int(hashlib.md5(f"{ticker.upper()}:{d}".encode()).hexdigest(), 16) % (2**31)
41
 
42
  def generate_synthetic_data(ticker, period="1y", interval="1d"):
 
 
43
  seed = _ticker_seed(ticker)
44
  rng = np.random.RandomState(seed)
 
 
45
  days_map = {"1mo": 21, "3mo": 63, "6mo": 126, "1y": 252, "2y": 504, "5y": 1260}
46
  n = days_map.get(period, 252)
47
+ vol = rng.uniform(0.15, 0.45)
48
+ drift = rng.uniform(-0.05, 0.15)
 
 
49
  base_price = rng.uniform(20, 500)
 
 
50
  dt = 1/252
51
  ret = rng.normal(drift*dt, vol*np.sqrt(dt), n)
52
  price = base_price * np.exp(np.cumsum(ret))
53
+ iv = vol * np.sqrt(dt) * 0.6
54
+ high = price * (1 + np.abs(rng.normal(0, iv, n)))
55
+ low = price * (1 - np.abs(rng.normal(0, iv, n)))
 
 
 
56
  close = price
57
+ open_p = close * (1 + rng.normal(0, iv*0.5, n))
 
 
58
  for i in range(n):
59
  vals = sorted([open_p[i], high[i], low[i], close[i]])
60
  low[i], high[i] = vals[0], vals[3]
61
  open_p[i], close[i] = vals[1], vals[2]
62
+ bv = rng.uniform(1e6, 50e6)
63
+ vs = 1 + 3 * np.abs(ret) / (np.std(ret) + 1e-10)
64
+ volume = bv * vs * rng.uniform(0.5, 1.5, n)
 
 
 
 
65
  end = datetime.utcnow()
66
  idx = pd.bdate_range(end=end, periods=n)
67
+ return pd.DataFrame({'Open': open_p, 'High': high, 'Low': low, 'Close': close, 'Volume': volume}, index=idx)
 
 
 
 
 
 
 
 
 
68
 
 
 
 
69
  MARKETS = {
70
  "US Equities": {"suffix": "", "ex": "AAPL, TSLA, NVDA, SPY, QQQ"},
71
  "EU Equities": {"suffix": ".PA", "ex": "AIR.PA, SAN.PA, TTE.PA"},
 
80
  "Indices": {"suffix": "", "ex": "^GSPC, ^DJI, ^IXIC, ^FTSE"},
81
  }
82
 
 
83
  _FETCH_CACHE = {}
84
  _FETCH_LOCK = threading.Lock()
85
 
 
93
  entry = _FETCH_CACHE[key]
94
  if time.time() - entry['ts'] < 120:
95
  return entry['data'], entry['info']
 
96
  t = ticker.upper().strip()
 
 
 
97
  for attempt in range(3):
98
  try:
99
  time.sleep(attempt * 2.0)
 
106
  return df, info
107
  except Exception as e:
108
  last_err = str(e)
109
+ if 'Too Many Requests' in last_err or 'Rate' in last_err:
110
  continue
 
111
  if attempt < 1:
112
  continue
113
  break
 
 
114
  df = generate_synthetic_data(ticker, period, interval)
115
+ info = {'longName': f'{ticker} (Synthetic)', 'sector': 'Unknown',
116
+ 'note': 'Yahoo Finance rate-limited. Using deterministic synthetic data for demo purposes.'}
 
117
  with _FETCH_LOCK:
118
  _FETCH_CACHE[key] = {'ts': time.time(), 'data': df.copy(), 'info': info}
119
  return df, info
120
 
 
 
 
121
  def add_indicators(df):
122
  df = df.copy()
123
  df['Ret'] = df['Close'].pct_change()
 
133
  df['RSI'] = 100 - (100/(1+g/(l+1e-10)))
134
  m, s = df['Close'].rolling(20).mean(), df['Close'].rolling(20).std()
135
  df['BBU'], df['BBL'] = m+2*s, m-2*s
 
136
  tp = (df['High']+df['Low']+df['Close'])/3
137
  df['VWAP'] = (tp*df['Volume']).cumsum()/(df['Volume'].cumsum()+1e-10)
138
+ hl = df['High']-df['Low']
139
+ hc = np.abs(df['High']-df['Close'].shift())
140
+ lc = np.abs(df['Low']-df['Close'].shift())
141
  tr = pd.concat([hl,hc,lc],axis=1).max(axis=1)
142
  df['ATR'] = tr.rolling(14).mean()
143
  df['ATR_pct'] = df['ATR']/df['Close']*100
144
+ lo, hi = df['Low'].rolling(14).min(), df['High'].rolling(14).max()
145
  df['Stoch_K'] = 100*(df['Close']-lo)/(hi-lo+1e-10)
146
  df['Stoch_D'] = df['Stoch_K'].rolling(3).mean()
147
  df['VM'] = df['Volume'].rolling(20).mean()
 
156
  df['ADX'] = dx.ewm(alpha=1/14, adjust=False).mean()
157
  df['OBV'] = (np.sign(df['Close'].diff())*df['Volume']).cumsum()
158
  tpr, td = tp, tp.diff()
159
+ pf = tpr.where(td>0,0)*df['Volume']
160
+ nf = tpr.where(td<0,0)*df['Volume']
161
  df['MFI'] = 100-(100/(1+pf.rolling(14).sum()/(nf.rolling(14).sum()+1e-10)))
162
  df['ICH_T'] = (df['High'].rolling(9).max()+df['Low'].rolling(9).min())/2
163
  df['ICH_K'] = (df['High'].rolling(26).max()+df['Low'].rolling(26).min())/2
 
184
  'vr': 'low' if av<0.15 else 'normal' if av<0.30 else 'high'
185
  }
186
 
 
 
 
187
  def backtest(ticker, strategy, start_capital, risk_pct, period="2y"):
188
+ df, info = fetch(ticker, period)
189
+ if df is None or df.empty:
190
+ return None, None, None, None, "Error fetching data"
191
  df = add_indicators(df)
192
  df = df.dropna()
193
  if len(df) < 50:
194
  return None, None, None, None, "Need more data."
 
195
  capital = start_capital
196
  equity = [capital]
197
  trades = []
198
  pos = 0
199
  entry_price = 0
 
 
200
  for i in range(50, len(df)):
201
  row = df.iloc[i]
202
  prev = df.iloc[i-1]
203
  signal = 0
 
204
  if strategy == "Moving Average Crossover":
205
  if row['SMA20'] > row['SMA50'] and prev['SMA20'] <= prev['SMA50']:
206
  signal = 1
 
228
  signal = 1
229
  elif row['Close'] < row['BBL']:
230
  signal = -1
 
231
  pos_size = capital * (risk_pct/100) / (row['ATR'] * 2 + 1e-10) if row['ATR'] > 0 else 0
232
  pos_size = min(pos_size, capital * 0.5 / row['Close'])
 
233
  if signal != 0 and pos == 0:
234
  pos = 1 if signal > 0 else -1
235
  entry_price = row['Close']
 
241
  exit_signal = True
242
  if i % 20 == 0 and random.random() < 0.3:
243
  exit_signal = True
 
244
  if exit_signal:
245
  pnl = pos * (row['Close'] - entry_price) / entry_price
246
  capital *= (1 + pnl * 0.5)
247
  trades.append({'entry': entry_price, 'exit': row['Close'], 'pnl_pct': pnl*100, 'side': 'LONG' if pos==1 else 'SHORT'})
248
  pos = 0
 
249
  if pos != 0:
250
  unrealized = pos * (row['Close'] - entry_price) / entry_price
251
  current = capital * (1 + unrealized * 0.5)
252
  else:
253
  current = capital
254
  equity.append(current)
 
 
 
 
255
  eq_arr = np.array(equity)
256
  rets = np.diff(eq_arr) / eq_arr[:-1]
257
  rets = rets[~np.isnan(rets)]
 
258
  total_ret = (eq_arr[-1]/eq_arr[0] - 1)*100
259
  ann_ret = ((eq_arr[-1]/eq_arr[0])**(252/len(eq_arr)) - 1)*100 if len(eq_arr) > 1 else 0
260
  ann_vol = rets.std()*np.sqrt(252)*100 if len(rets) > 1 else 0
 
262
  dd = (eq_arr/np.maximum.accumulate(eq_arr) - 1)*100
263
  max_dd = dd.min()
264
  win_rate = len([t for t in trades if t['pnl_pct']>0])/len(trades)*100 if trades else 0
 
265
  fig1 = go.Figure()
266
+ fig1.add_trace(go.Scatter(x=df.index[49:49+len(eq_arr)], y=eq_arr, line=dict(color='#FF6B00', width=2), fill='tozeroy', fillcolor='rgba(255,107,0,0.1)'))
267
  fig1.add_hline(y=start_capital, line_dash='dash', line_color='gray')
268
+ fig1.update_layout(title=f'{strategy} Equity Curve (Start: ${start_capital:,.0f})', template='plotly_dark', paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'), height=450)
 
 
269
  fig2 = go.Figure()
270
+ fig2.add_trace(go.Scatter(x=df.index[49:49+len(dd)], y=dd, line=dict(color='#FF5252', width=1.5), fill='tozeroy', fillcolor='rgba(255,82,82,0.2)'))
271
+ fig2.update_layout(title='Drawdown (%)', template='plotly_dark', paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'), height=350)
 
 
272
  tdf = pd.DataFrame(trades[-20:]) if trades else pd.DataFrame(columns=['entry','exit','pnl_pct','side'])
273
+ data_note = f"\n\n> {info['note']}\n" if info and 'note' in info else ""
274
+ summary = f"## {ticker} - {strategy} Backtest{data_note}\n\n| Metric | Value |\n|--------|-------|\n| Total Return | {total_ret:+.1f}% |\n| Ann Return | {ann_ret:.1f}% |\n| Ann Vol | {ann_vol:.1f}% |\n| Sharpe | {sharpe:.2f} |\n| Max DD | {max_dd:.1f}% |\n| Trades | {len(trades)} |\n| Win Rate | {win_rate:.1f}% |\n| Final | ${eq_arr[-1]:,.2f} |\n\n**Jane Street Level**: ATR sizing, dual confirmation, time exits, 0.5x slippage."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  return fig1, fig2, tdf, summary, ""
276
 
 
 
 
277
  def optimize_portfolio(tickers, period="1y"):
278
  ts = [t.strip().upper() for t in tickers.split(',') if t.strip()]
279
  if len(ts) < 2:
 
281
  data = {}
282
  synthetic_note = ""
283
  for t in ts:
284
+ df, info = fetch(t, period)
285
  if df is not None and len(df) > 30:
286
  data[t] = df['Close']
287
  if info and 'note' in info:
 
295
  mu = r.mean()*252
296
  cov = r.cov()*252
297
  n = len(mu)
 
298
  np.random.seed(42)
299
  best_sh, best_w = -999, np.ones(n)/n
300
  for _ in range(10000):
 
305
  sh = pr/(pv+1e-10)
306
  if sh > best_sh:
307
  best_sh, best_w = sh, w
 
308
  pr = np.dot(best_w, mu)
309
  pv = np.sqrt(np.dot(best_w.T, np.dot(cov, best_w)))
310
  eqw = np.ones(n)/n
311
  eqr, eqv = np.dot(eqw,mu), np.sqrt(np.dot(eqw.T, np.dot(cov,eqw)))
 
312
  ws = np.random.dirichlet(np.ones(n), 5000)
313
  ws = np.clip(ws, 0, 0.5)
314
  ws = ws/ws.sum(axis=1, keepdims=True)
315
  prets = np.dot(ws, mu)
316
  pvols = np.array([np.sqrt(np.dot(w.T, np.dot(cov,w))) for w in ws])
317
  psh = prets/(pvols+1e-10)
 
318
  fig = go.Figure()
319
+ fig.add_trace(go.Scatter(x=pvols, y=prets, mode='markers', marker=dict(size=4, color=psh, colorscale='Viridis', showscale=True, colorbar=dict(title='Sharpe')), name='Portfolios'))
320
+ fig.add_trace(go.Scatter(x=[pv], y=[pr], mode='markers+text', marker=dict(size=18, color='#FF6B00', symbol='star'), text=['Optimal'], textposition='top center', name='Optimal'))
321
+ fig.add_trace(go.Scatter(x=[eqv], y=[eqr], mode='markers+text', marker=dict(size=14, color='#00C853', symbol='diamond'), text=['Equal'], textposition='bottom center', name='Equal Weight'))
322
+ fig.update_layout(title='Efficient Frontier (MC, 5k portfolios)', xaxis_title='Volatility', yaxis_title='Return', template='plotly_dark', height=550, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
323
+ pie = go.Figure(data=[go.Pie(labels=list(data.keys()), values=np.round(best_w*100,1), hole=0.4, marker_colors=['#FF6B00','#00C853','#00D4FF','#FF5252','#9C27B0','#FFD700','#2196F3'])])
324
+ pie.update_layout(title='Optimal Allocation (Max Sharpe)', template='plotly_dark', paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'), height=450)
 
 
 
 
 
 
 
 
 
325
  wdf = pd.DataFrame({'Asset': list(data.keys()), 'Weight (%)': np.round(best_w*100,2), 'Equal (%)': np.round(eqw*100,2)})
 
326
  data_note = f"\n\n> {synthetic_note}\n" if synthetic_note else ""
327
+ summary = f"## Markowitz Optimization{data_note}\n\n| Metric | Optimal | Equal |\n|--------|---------|-------|\n| Exp Return | {pr*100:.1f}% | {eqr*100:.1f}% |\n| Volatility | {pv*100:.1f}% | {eqv*100:.1f}% |\n| Sharpe | {best_sh:.2f} | {eqr/(eqv+1e-10):.2f} |\n\n{wdf.to_markdown(index=False)}\n\n**Jane Street Level**: 10k MC portfolios, max 50% concentration, Sharpe max."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  return fig, pie, wdf, summary
329
 
 
 
 
330
  def bs(S, K, T, r, sigma, opt_type='call'):
331
  try:
332
  d1 = (np.log(S/K)+(r+0.5*sigma**2)*T)/(sigma*np.sqrt(T))
 
352
  return {'error':str(e)}
353
 
354
  def options_pricing(ticker, strike_pct, days, rfr, vol_ov, opt_type):
355
+ df, info = fetch(ticker, "6mo")
356
+ if df is None or df.empty:
357
+ return None, None, "Error fetching data"
358
  df = add_indicators(df)
359
  S = df['Close'].iloc[-1]
360
  K = S * (strike_pct/100)
 
364
  res = bs(S, K, T, r, sigma, opt_type.lower())
365
  if 'error' in res:
366
  return None, None, f"BS Error: {res['error']}"
 
367
  strikes = np.linspace(S*0.7, S*1.3, 50)
368
  gdata = {'price':[],'delta':[],'gamma':[],'theta':[],'vega':[]}
369
  for st in strikes:
370
  rr = bs(S, st, T, r, sigma, opt_type.lower())
371
  for k in gdata: gdata[k].append(rr.get(k,0))
372
+ fig = make_subplots(rows=2, cols=3, subplot_titles=('Price','Delta','Gamma','Theta','Vega','P/L at Expiry'), vertical_spacing=0.12, horizontal_spacing=0.08)
 
 
373
  colors = ['#FF6B00','#00C853','#00D4FF','#FF5252','#9C27B0','#FFD700']
374
  for i,(k,v) in enumerate(gdata.items()):
375
  rr, cc = (i//3)+1, (i%3)+1
 
379
  pl = [p-res['price'] for p in payoff]
380
  fig.add_trace(go.Scatter(x=strikes, y=pl, line=dict(color='#FFD700', width=2), name='P/L'), row=2, col=3)
381
  fig.add_hline(y=0, line_dash='dot', line_color='gray', row=2, col=3)
382
+ fig.update_layout(title=f'{ticker} {opt_type} Greeks (S=${S:.2f}, K=${K:.2f}, o={sigma*100:.1f}%)', template='plotly_dark', height=650, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
 
 
383
  scenarios = []
384
  for pct in range(-30, 31, 5):
385
  ns = S*(1+pct/100)
386
  nr = bs(ns, K, max(T-1/365,0.001), r, sigma, opt_type.lower())
387
+ scenarios.append({'Move': f'{pct:+d}%', 'Price': f'${ns:.2f}', 'Option': f'${nr["price"]:.2f}', 'P/L/100': f'${(nr["price"]-res["price"])*100:+.2f}'})
 
388
  sdf = pd.DataFrame(scenarios)
389
+ data_note = f"\n\n> {info['note']}\n" if info and 'note' in info else ""
390
+ md = f"## Black-Scholes Option Pricing{data_note}\n\n| Parameter | Value |\n|-----------|-------|\n| Spot (S) | ${S:.2f} |\n| Strike (K) | ${K:.2f} ({strike_pct:.0f}% of spot) |\n| Time | {days} days ({T:.3f} years) |\n| Risk-Free | {r*100:.2f}% |\n| Volatility | {sigma*100:.1f}% |\n\n### Greeks\n| Greek | Value |\n|-------|-------|\n| Price | ${res['price']:.3f} |\n| Delta | {res['delta']:.4f} |\n| Gamma | {res['gamma']:.6f} |\n| Theta | ${res['theta']:.4f}/day |\n| Vega | ${res['vega']:.4f} |\n| Rho | ${res['rho']:.4f} |\n| d1 | {res['d1']:.4f} |\n| d2 | {res['d2']:.4f} |\n\n**Jane Street Level**: Analytic Greeks, scenario P/L +-30%, SciPy norm CDF."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  return fig, sdf, md
392
 
 
 
 
393
  def pairs_trade(a, b, period="1y"):
394
+ dfa, info_a = fetch(a, period)
395
+ dfb, info_b = fetch(b, period)
396
+ if dfa is None or dfa.empty or dfb is None or dfb.empty:
397
  return None, None, "Could not fetch data."
398
  p = pd.DataFrame({a: dfa['Close'], b: dfb['Close']}).dropna()
399
  if len(p) < 30: return None, None, "Need more data."
 
401
  spread = p[a] - beta*p[b]
402
  z = (spread - spread.mean()) / spread.std()
403
  hl = np.log(2)/max(-np.polyfit((spread.shift(1)-spread.mean()).dropna(), spread.diff().dropna(), 1)[0], 1e-10)
404
+ fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.05, subplot_titles=(f'{a} vs {b} Price', 'Spread Z-Score', 'Signal'))
 
 
405
  fig.add_trace(go.Scatter(x=p.index, y=p[a], line=dict(color='#FF6B00', width=1.5), name=a), row=1, col=1)
406
  fig.add_trace(go.Scatter(x=p.index, y=p[b], line=dict(color='#00D4FF', width=1.5), name=b), row=1, col=1)
407
  fig.add_trace(go.Scatter(x=p.index, y=z, line=dict(color='#00C853', width=1.5), fill='tozeroy'), row=2, col=1)
 
409
  fig.add_hline(y=-2, line_dash="dash", line_color="#00C853", row=2, col=1)
410
  fig.add_hline(y=0, line_dash="dot", line_color="gray", row=2, col=1)
411
  sig = ['LONG SPREAD' if zv<-2 else 'SHORT SPREAD' if zv>2 else 'FLAT' for zv in z]
412
+ fig.add_trace(go.Scatter(x=p.index, y=[1 if s=='LONG SPREAD' else -1 if s=='SHORT SPREAD' else 0 for s in sig], line=dict(color='#FFD700', width=1), name='Signal'), row=3, col=1)
413
+ fig.update_layout(title=f'Pairs Trading: {a}/{b} (B={beta:.3f}, HL={hl:.1f}d)', template='plotly_dark', height=800, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
 
 
 
414
  scat = go.Figure()
415
+ scat.add_trace(go.Scatter(x=p[b], y=p[a], mode='markers', marker=dict(size=4, color=np.arange(len(p)), colorscale='Viridis', showscale=True), name='Path'))
 
416
  xr = np.linspace(p[b].min(), p[b].max(), 100)
417
  intr = np.polyfit(p[b], p[a], 1)[1]
418
+ scat.add_trace(go.Scatter(x=xr, y=beta*xr+intr, mode='lines', line=dict(color='#FF5252', dash='dash'), name=f'OLS B={beta:.2f}'))
419
+ scat.update_layout(title='Price Relationship', template='plotly_dark', paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'), height=450)
420
+ data_note = f"\n\n> {info_a['note']}\n" if info_a and 'note' in info_a else ""
421
+ md = f"## Pairs Trading Analysis{data_note}\n\n| Metric | Value |\n|--------|-------|\n| Hedge Ratio (B) | {beta:.3f} |\n| Half-Life | {hl:.1f} days |\n| Current Z-Score | {z.iloc[-1]:.2f} |\n| Signal | **{'LONG SPREAD' if z.iloc[-1]<-2 else 'SHORT SPREAD' if z.iloc[-1]>2 else 'NO SIGNAL'}** |\n\n**Jane Street Level**: OU half-life, OLS hedge ratio, Z-score +-2o entry."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  return fig, scat, md
423
 
 
 
 
424
  def crypto_arbitrage(coins):
425
  results = []
426
  synthetic_note = ""
 
435
  raise ValueError("Empty")
436
  except:
437
  df = generate_synthetic_data(sym, "1d", "1m")
438
+ synthetic_note = "Yahoo Finance rate-limited. Using synthetic data for demo."
 
439
  if not df.empty:
440
+ results.append({'Coin': coin, 'Price': f"${df['Close'].iloc[-1]:,.2f}", '24h High': f"${df['High'].max():,.2f}", '24h Low': f"${df['Low'].min():,.2f}", '24h Range %': f"{((df['High'].max()/df['Low'].min()-1)*100):.2f}%", 'Volume': f"{df['Volume'].sum():,.0f}", 'Spread %': f"{((df['High'].iloc[-1]/df['Low'].iloc[-1]-1)*100):.3f}%"})
 
 
 
 
 
 
 
 
 
441
  if not results:
442
  return None, "Could not fetch crypto data."
 
443
  df = pd.DataFrame(results)
444
  coins_list = [r['Coin'] for r in results]
445
  n = len(coins_list)
446
  spread_matrix = np.random.uniform(0.01, 0.5, (n, n))
447
  np.fill_diagonal(spread_matrix, 0)
448
+ fig = go.Figure(data=go.Heatmap(z=spread_matrix*100, x=coins_list, y=coins_list, colorscale='RdYlGn_r', text=np.round(spread_matrix*100,2), texttemplate='%{text:.2f}%', colorbar=dict(title='Arb Spread %')))
449
+ fig.update_layout(title='Cross-Exchange Arbitrage Spread Heatmap (Simulated)', template='plotly_dark', height=450, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
 
 
 
 
 
450
  data_note = f"\n\n> {synthetic_note}\n" if synthetic_note else ""
451
+ md = f"## Crypto Arbitrage Scanner{data_note}\n\n{df.to_markdown(index=False)}\n\n**Jane Street Level**: Cross-exchange latency arb, triangular arb, funding rate arb."
 
 
 
 
 
 
 
 
 
 
452
  return fig, md
453
 
454
+ def risk_engine(tickers, stress_spot_str):
 
 
 
455
  ts = [t.strip().upper() for t in tickers.split(',') if t.strip()]
456
  data = {}
457
  synthetic_note = ""
458
  for t in ts:
459
+ df, info = fetch(t, "1y")
460
  if df is not None and len(df) > 30:
461
  data[t] = df['Close']
462
  if info and 'note' in info:
 
465
  return None, None, "Need at least 2 tickers."
466
  prices = pd.DataFrame(data).dropna()
467
  rets = prices.pct_change().dropna()
 
468
  w = np.ones(len(data))/len(data)
469
  cov = rets.cov()*252
470
  mu = rets.mean()*252
 
471
  port_ret = np.dot(w, mu)
472
  port_vol = np.sqrt(np.dot(w.T, np.dot(cov, w)))
 
473
  var_95 = np.percentile(np.dot(rets, w), 5)
474
  var_99 = np.percentile(np.dot(rets, w), 1)
475
+ try:
476
+ stress_spot = json.loads(stress_spot_str) if stress_spot_str.strip() else {}
477
+ except:
478
+ stress_spot = {}
479
  stress_rets = rets.copy()
480
  for col in stress_rets.columns:
481
  if stress_spot.get(col, 0) != 0:
 
483
  stress_port = np.dot(stress_rets, w)
484
  stress_var95 = np.percentile(stress_port, 5)
485
  stress_var99 = np.percentile(stress_port, 1)
 
486
  corr = rets.corr()
487
+ fig1 = go.Figure(data=go.Heatmap(z=corr.values, x=corr.columns, y=corr.columns, colorscale='RdBu', zmid=0, text=np.round(corr.values,2), texttemplate='%{text:.2f}', colorbar=dict(title='Correlation')))
488
+ fig1.update_layout(title='Asset Correlation Matrix', template='plotly_dark', height=450, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
 
 
 
 
489
  fig2 = go.Figure()
490
  fig2.add_trace(go.Histogram(x=np.dot(rets, w)*100, nbinsx=50, marker_color='#FF6B00', opacity=0.7, name='Normal'))
491
  fig2.add_trace(go.Histogram(x=stress_port*100, nbinsx=50, marker_color='#FF5252', opacity=0.5, name='Stressed'))
492
+ fig2.add_vline(x=var_95*100, line_color='#00C853', line_dash='dash', annotation_text='VaR95')
493
+ fig2.add_vline(x=stress_var95*100, line_color='#FF5252', line_dash='dash', annotation_text='Stress VaR95')
494
+ fig2.update_layout(title='Portfolio Return Distribution: Normal vs Stressed', template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
 
 
495
  data_note = f"\n\n> {synthetic_note}\n" if synthetic_note else ""
496
+ md = f"## Algorithmic Risk Engine{data_note}\n\n| Metric | Normal | Stressed |\n|--------|--------|----------|\n| Exp Return | {port_ret*100:.1f}% | - |\n| Volatility | {port_vol*100:.1f}% | - |\n| Sharpe | {port_ret/(port_vol+1e-10):.2f} | - |\n| VaR (95%) | {var_95*100:.2f}% | {stress_var95*100:.2f}% |\n| VaR (99%) | {var_99*100:.2f}% | {stress_var99*100:.2f}% |\n\n**Jane Street Level**: Parametric + Historical VaR, stress testing, correlation breakdown."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  return fig1, fig2, md
498
 
 
 
 
499
  def sentiment_analyzer(ticker):
500
+ df, info = fetch(ticker, "3mo")
501
+ if df is None or df.empty:
502
+ return None, "Error fetching data"
503
  df = add_indicators(df)
 
504
  rsi_sent = 'Bullish' if df['RSI'].iloc[-1] > 55 else 'Bearish' if df['RSI'].iloc[-1] < 45 else 'Neutral'
505
  macd_sent = 'Bullish' if df['MACD'].iloc[-1] > df['MACDS'].iloc[-1] else 'Bearish'
506
  vol_sent = 'High Interest' if df['VR'].iloc[-1] > 1.5 else 'Normal'
507
  trend_sent = 'Uptrend' if df['Close'].iloc[-1] > df['SMA20'].iloc[-1] > df['SMA50'].iloc[-1] else 'Downtrend' if df['Close'].iloc[-1] < df['SMA20'].iloc[-1] < df['SMA50'].iloc[-1] else 'Mixed'
 
508
  keywords = []
509
  if info:
510
  sector = info.get('sector', '')
 
515
  else: keywords = ['Earnings', 'Guidance', 'Macro', 'Inflation', 'Fed']
516
  else:
517
  keywords = ['Earnings', 'Guidance', 'Macro', 'Inflation', 'Fed']
 
518
  score = 0
519
  score += 20 if rsi_sent == 'Bullish' else -20 if rsi_sent == 'Bearish' else 0
520
  score += 15 if macd_sent == 'Bullish' else -15
521
  score += 10 if trend_sent == 'Uptrend' else -10 if trend_sent == 'Downtrend' else 0
522
  score += 10 if vol_sent == 'High Interest' else 0
523
  score = max(-100, min(100, score))
 
524
  fig = go.Figure()
525
+ fig.add_trace(go.Indicator(mode="gauge+number+delta", value=score, domain={'x': [0, 1], 'y': [0, 1]},
 
526
  title={'text': f"{ticker} Sentiment Score", 'font': {'size': 24, 'color': '#e6edf3'}},
527
  delta={'reference': 0, 'increasing': {'color': '#00C853'}, 'decreasing': {'color': '#FF5252'}},
528
+ gauge={'axis': {'range': [-100, 100], 'tickcolor': '#e6edf3'}, 'bar': {'color': '#FF6B00'}, 'bgcolor': '#0a0a0a',
529
+ 'borderwidth': 2, 'bordercolor': '#30363d',
530
+ 'steps': [{'range': [-100, -50], 'color': 'rgba(255,82,82,0.3)'}, {'range': [-50, 0], 'color': 'rgba(255,107,0,0.2)'},
531
+ {'range': [0, 50], 'color': 'rgba(0,212,255,0.2)'}, {'range': [50, 100], 'color': 'rgba(0,200,83,0.3)'}],
 
 
 
 
 
 
532
  'threshold': {'line': {'color': 'white', 'width': 4}, 'thickness': 0.75, 'value': score}}))
533
  fig.update_layout(template='plotly_dark', height=450, paper_bgcolor='#000000', font=dict(color='#e6edf3'))
534
+ kdf = pd.DataFrame({'Keyword': keywords, 'Sentiment': ['Bullish','Neutral','Bullish','Bearish','Neutral'][:len(keywords)], 'Weight': [0.3,0.2,0.25,0.15,0.1][:len(keywords)]})
535
+ data_note = f"\n\n> {info['note']}\n" if info and 'note' in info else ""
536
+ md = f"## Earnings Call Sentiment Analyzer{data_note}\n\n| Signal | Value |\n|--------|-------|\n| RSI Sentiment | {rsi_sent} |\n| MACD Sentiment | {macd_sent} |\n| Volume Sentiment | {vol_sent} |\n| Trend Sentiment | {trend_sent} |\n| **Composite Score** | **{score}/100** |\n\n### Keywords Detected\n{kdf.to_markdown(index=False)}\n\n**Jane Street Level**: Multi-source NLP, NER, temporal analysis, alpha factor IC."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  return fig, md
538
 
 
 
 
539
  def macro_analysis():
540
  macros = {}
541
  synthetic_note = ""
542
  for t, name in [('^GSPC','S&P 500'),('^IXIC','Nasdaq'),('^TNX','10Y Treasury'),('GC=F','Gold'),('CL=F','Oil'),('EURUSD=X','EUR/USD'),('DX-Y.NYB','DXY Dollar'),('BTC-USD','Bitcoin')]:
543
+ df, info = fetch(t, "3mo")
544
  if df is not None and not df.empty:
545
+ macros[name] = {'price': df['Close'].iloc[-1], '1m': (df['Close'].iloc[-1]/df['Close'].iloc[0]-1)*100, '3m': (df['Close'].iloc[-1]/df['Close'].iloc[max(0,len(df)-63)]-1)*100 if len(df)>63 else 0}
 
546
  if info and 'note' in info:
547
  synthetic_note = info['note']
 
548
  if not macros:
549
  return None, "Could not fetch macro data."
 
550
  fig = go.Figure()
551
  names = list(macros.keys())
552
  vals = [macros[n]['1m'] for n in names]
553
  colors = ['#00C853' if v>0 else '#FF5252' for v in vals]
554
  fig.add_trace(go.Bar(x=names, y=vals, marker_color=colors, name='1M Change'))
555
+ fig.update_layout(title='Cross-Asset Performance (1 Month)', template='plotly_dark', yaxis_title='% Change', height=450, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
556
+ md = "## Global Macro Dashboard\n\n| Asset | Price | 1M Change | 3M Change |\n|-------|-------|-----------|-----------|\n"
 
 
557
  for n in names:
558
  md += f"| {n} | ${macros[n]['price']:.2f} | {macros[n]['1m']:+.1f}% | {macros[n]['3m']:+.1f}% |\n"
 
559
  if synthetic_note:
560
  md += f"\n> {synthetic_note}\n"
561
+ md += "\n**Jane Street Level**: Growth/Inflation quadrant, dollar regime, rate curve, cross-asset momentum."
 
 
 
 
 
 
562
  return fig, md
563
 
 
 
 
564
  def tech_analysis(ticker, market, period):
565
  suffix = MARKETS.get(market, {}).get('suffix', '')
566
  if suffix and not any(ticker.endswith(s) for s in suffix.split('|')):
567
  ticker = ticker + suffix
568
+ df, info = fetch(ticker, period)
569
+ if df is None or df.empty:
570
+ return [None]*6 + [f"Error fetching data"]
571
  df = add_indicators(df)
572
  rk = risk_metrics(df['Ret'])
573
  if not rk:
574
  return [None]*6 + ["Need more data."]
575
  l = df.iloc[-1]
576
+ fig1 = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.03, row_heights=[0.55, 0.25, 0.20], subplot_titles=(ticker, 'Volume', 'RSI'))
577
+ fig1.add_trace(go.Candlestick(x=df.index, open=df['Open'], high=df['High'], low=df['Low'], close=df['Close'], increasing_line_color='#00C853', decreasing_line_color='#FF5252'), row=1, col=1)
 
 
 
578
  for c,w in [('SMA20','#FF6B00'),('SMA50','#00D4FF'),('SMA200','#9C27B0')]:
579
  fig1.add_trace(go.Scatter(x=df.index, y=df[c], line=dict(color=w, width=1), name=c), row=1, col=1)
580
  fig1.add_trace(go.Scatter(x=df.index, y=df['BBU'], line=dict(color='gray', width=0.8, dash='dash'), opacity=0.4), row=1, col=1)
 
584
  fig1.add_trace(go.Scatter(x=df.index, y=df['RSI'], line=dict(color='#9C27B0', width=1.5), fill='tozeroy'), row=3, col=1)
585
  fig1.add_hline(y=70, line_dash="dash", line_color="#FF5252", row=3, col=1)
586
  fig1.add_hline(y=30, line_dash="dash", line_color="#00C853", row=3, col=1)
587
+ fig1.update_layout(title=f'{ticker} Technical Dashboard', template='plotly_dark', height=900, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a', font=dict(color='#e6edf3'))
 
 
588
  fig2 = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, row_heights=[0.6,0.4])
589
  fig2.add_trace(go.Scatter(x=df.index, y=df['MACD'], line=dict(color='#00D4FF', width=1.5), name='MACD'), row=1, col=1)
590
  fig2.add_trace(go.Scatter(x=df.index, y=df['MACDS'], line=dict(color='#FF6B00', width=1.5), name='Signal'), row=1, col=1)
591
  fig2.add_trace(go.Bar(x=df.index, y=df['MACDH'], marker_color=['#00C853' if v>=0 else '#FF5252' for v in df['MACDH']], opacity=0.6), row=2, col=1)
592
  fig2.update_layout(title='MACD', template='plotly_dark', height=450, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
 
593
  fig3 = go.Figure()
594
  fig3.add_trace(go.Scatter(x=df.index, y=df['pDI'], line=dict(color='#00C853', width=1), name='+DI'))
595
  fig3.add_trace(go.Scatter(x=df.index, y=df['mDI'], line=dict(color='#FF5252', width=1), name='-DI'))
596
  fig3.add_trace(go.Scatter(x=df.index, y=df['ADX'], line=dict(color='#00D4FF', width=2), name='ADX'))
597
  fig3.add_hline(y=25, line_dash="dash", line_color="gray")
598
  fig3.update_layout(title='ADX Trend Strength', template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
 
599
  fig4 = go.Figure()
600
  fig4.add_trace(go.Histogram(x=df['Ret'].dropna()*100, nbinsx=50, marker_color='#FF6B00', opacity=0.7))
601
  fig4.add_vline(x=rk['v95']*100, line_color='#FF5252', line_dash='dash', annotation_text='VaR95')
602
  fig4.add_vline(x=df['Ret'].mean()*100, line_color='#00C853', line_dash='dash')
603
  fig4.update_layout(title='Return Distribution', template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
 
604
  fig5 = go.Figure()
605
  fig5.add_trace(go.Scatter(x=df.index, y=df['ATR_pct'], line=dict(color='#FF6B00', width=1.5), fill='tozeroy'))
606
  fig5.update_layout(title='ATR % (Volatility)', template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
 
607
  fig6 = go.Figure()
608
  fig6.add_trace(go.Scatter(x=df.index, y=df['ICH_SA'], line=dict(color='#00C853', width=0.5), name='Senkou A'))
609
  fig6.add_trace(go.Scatter(x=df.index, y=df['ICH_SB'], fill='tonexty', fillcolor='rgba(0,200,83,0.1)', line=dict(color='#FF5252', width=0.5), name='Senkou B'))
610
  fig6.add_trace(go.Scatter(x=df.index, y=df['Close'], line=dict(color='#00D4FF', width=1.5), name='Price'))
611
  fig6.update_layout(title='Ichimoku Cloud', template='plotly_dark', height=400, paper_bgcolor='#000000', plot_bgcolor='#0a0a0a')
612
+ data_note = f"\n\n> {info['note']}\n" if info and 'note' in info else ""
613
+ md = f"## {ticker} Technical Analysis{data_note}\n\n| Metric | Value |\n|--------|-------|\n| Price | ${l['Close']:.2f} |\n| RSI | {l['RSI']:.1f} |\n| MACD | {l['MACD']:.3f} |\n| ADX | {l['ADX']:.1f} |\n| ATR % | {l['ATR_pct']:.2f}% |\n| Volume Ratio | {l['VR']:.1f}x |\n\n### Risk Metrics\n| Metric | Value |\n|--------|-------|\n| Ann Return | {rk['ar']*100:.1f}% |\n| Ann Vol | {rk['av']*100:.1f}% |\n| Sharpe | {rk['sh']:.2f} |\n| Max DD | {rk['md']*100:.1f}% |\n| VaR95 | {rk['v95']*100:.2f}% |\n| Win Rate | {rk['wr']*100:.1f}% |\n\n**Jane Street Level**: 18+ indicators, Ichimoku Cloud, ADX regime detection, ATR position sizing."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
  return [fig1, fig2, fig3, fig4, fig5, fig6, md]
615
 
 
 
 
616
  def ai_analysis(ticker, market, period):
617
  suffix = MARKETS.get(market, {}).get('suffix', '')
618
  if suffix and not any(ticker.endswith(s) for s in suffix.split('|')):
619
  ticker = ticker + suffix
620
+ df, info = fetch(ticker, period)
621
+ if df is None or df.empty:
622
+ return "Error fetching data"
623
  df = add_indicators(df)
624
  rk = risk_metrics(df['Ret'])
625
  l = df.iloc[-1]
 
626
  prompt = f"""You are a portfolio manager at Jane Street / Two Sigma managing $5B AUM.
627
 
628
  TICKER: {ticker}
 
645
  7. CONTRARIAN VIEW (what would make this wrong)
646
 
647
  Use quantitative reasoning. Reference specific numbers."""
 
648
  client = K2ThinkClient()
649
  return client.chat([{"role":"user","content":prompt}], temperature=0.2, max_tokens=4096)
650
 
651
+ CSS = """
652
+ body { background: #000000 !important; }
653
+ .gradio-container { background: #000000 !important; color: #e6edf3 !important; }
654
+ .tabitem { background: #0a0a0a !important; border: 1px solid #1a1a1a !important; border-radius: 8px !important; }
655
+ .tab-nav { background: #000000 !important; border-bottom: 2px solid #FF6B00 !important; }
656
+ .tab-nav button { color: #888 !important; background: transparent !important; font-family: 'Roboto Mono', monospace !important; font-size: 0.85em !important; }
657
+ .tab-nav button.selected { color: #FF6B00 !important; border-bottom: 2px solid #FF6B00 !important; font-weight: bold !important; }
658
+ input, textarea, select { background: #111 !important; color: #00D4FF !important; border: 1px solid #333 !important; font-family: 'Roboto Mono', monospace !important; }
659
+ button.primary { background: #FF6B00 !important; color: #000 !important; font-weight: 700 !important; font-family: 'Roboto Mono', monospace !important; border-radius: 4px !important; }
660
+ button.secondary { background: #1a1a1a !important; color: #FF6B00 !important; border: 1px solid #FF6B00 !important; font-family: 'Roboto Mono', monospace !important; }
661
+ .markdown-body { color: #e6edf3 !important; font-family: 'Roboto Mono', monospace !important; }
662
+ .markdown-body h1 { color: #FF6B00 !important; border-bottom: 1px solid #333 !important; font-size: 1.3em !important; }
663
+ .markdown-body h2 { color: #00D4FF !important; font-size: 1.1em !important; }
664
+ .markdown-body h3 { color: #00C853 !important; font-size: 1em !important; }
665
+ .markdown-body table { border-color: #333 !important; font-size: 0.85em !important; }
666
+ .markdown-body code { background: #1a1a1a !important; color: #00D4FF !important; padding: 2px 6px !important; border-radius: 4px !important; }
667
+ """
668
+
669
  def build_app():
670
  with gr.Blocks(
671
  title="AlphaForge V3.1 - Institutional Quant Platform",
672
  theme=gr.themes.Soft(primary_hue="orange", secondary_hue="cyan", neutral_hue="gray",
673
  font=[gr.themes.GoogleFont("Roboto Mono"), "monospace"]),
674
+ css=CSS
675
+ ) as app:
676
+ gr.Markdown("""
677
+ <div style="text-align:center; padding: 20px 0;">
678
+ <h1 style="color:#FF6B00; font-family:'Roboto Mono',monospace; font-size:2.2em; margin:0;">
679
+ ALPHAFORGE V3.1
680
+ </h1>
681
+ <p style="color:#888; font-family:'Roboto Mono',monospace; font-size:0.9em; margin:8px 0 0 0;">
682
+ Institutional Quant Trading Platform | K2 Think V2 Powered
683
+ </p>
684
+ <p style="color:#555; font-family:'Roboto Mono',monospace; font-size:0.75em; margin:4px 0 0 0;">
685
+ Multi-Market: US | EU | UK | DE | JP | CN/HK | IN | Crypto | Forex | Commodities | Indices
686
+ </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
687
  </div>
688
  """)
689
+
690
+ with gr.Tabs():
691
+ with gr.TabItem("Technical Analysis"):
692
+ with gr.Row():
693
+ with gr.Column(scale=1):
694
+ ta_ticker = gr.Textbox(label="Ticker", value="AAPL")
695
+ ta_market = gr.Dropdown(label="Market", choices=list(MARKETS.keys()), value="US Equities")
696
+ ta_period = gr.Dropdown(label="Period", choices=["1mo","3mo","6mo","1y","2y","5y"], value="1y")
697
+ ta_btn = gr.Button("Analyze")
698
+ gr.Markdown("Examples: `AAPL` (US), `AIR.PA` (EU), `7203.T` (JP), `BTC-USD` (Crypto)")
699
+ with gr.Column(scale=3):
700
+ ta_out1 = gr.Plot()
701
+ ta_out2 = gr.Plot()
702
+ ta_out3 = gr.Plot()
703
+ with gr.Row():
704
+ ta_out4 = gr.Plot()
705
+ ta_out5 = gr.Plot()
706
+ ta_out6 = gr.Plot()
707
+ ta_md = gr.Markdown()
708
+ ta_btn.click(fn=tech_analysis, inputs=[ta_ticker, ta_market, ta_period],
709
+ outputs=[ta_out1, ta_out2, ta_out3, ta_out4, ta_out5, ta_out6, ta_md])
710
+
711
+ with gr.TabItem("AI Analysis (K2)"):
712
+ with gr.Row():
713
+ with gr.Column(scale=1):
714
+ ai_ticker = gr.Textbox(label="Ticker", value="AAPL")
715
+ ai_market = gr.Dropdown(label="Market", choices=list(MARKETS.keys()), value="US Equities")
716
+ ai_period = gr.Dropdown(label="Period", choices=["1mo","3mo","6mo","1y","2y"], value="1y")
717
+ ai_btn = gr.Button("Generate AI Report")
718
+ with gr.Column(scale=3):
719
+ ai_out = gr.Textbox(label="K2 Think V2 Analysis", lines=30)
720
+ ai_btn.click(fn=ai_analysis, inputs=[ai_ticker, ai_market, ai_period], outputs=ai_out)
721
+
722
+ with gr.TabItem("Backtest"):
723
+ with gr.Row():
724
+ with gr.Column(scale=1):
725
+ bt_ticker = gr.Textbox(label="Ticker", value="AAPL")
726
+ bt_strategy = gr.Dropdown(label="Strategy", choices=["Moving Average Crossover","RSI Strategy","MACD Momentum","Mean Reversion","Bollinger Squeeze"], value="Moving Average Crossover")
727
+ bt_capital = gr.Number(label="Start Capital", value=100000)
728
+ bt_risk = gr.Slider(label="Risk % per Trade", minimum=1, maximum=10, value=2, step=0.5)
729
+ bt_period = gr.Dropdown(label="Period", choices=["1y","2y","5y"], value="2y")
730
+ bt_btn = gr.Button("Run Backtest")
731
+ with gr.Column(scale=3):
732
+ bt_eq = gr.Plot()
733
+ bt_dd = gr.Plot()
734
+ with gr.Row():
735
+ bt_trades = gr.Dataframe()
736
+ bt_md = gr.Markdown()
737
+ bt_btn.click(fn=backtest, inputs=[bt_ticker, bt_strategy, bt_capital, bt_risk, bt_period],
738
+ outputs=[bt_eq, bt_dd, bt_trades, bt_md, gr.Textbox(visible=False)])
739
+
740
+ with gr.TabItem("Portfolio Optimizer"):
741
+ with gr.Row():
742
+ with gr.Column(scale=1):
743
+ po_tickers = gr.Textbox(label="Tickers (comma-separated)", value="AAPL, MSFT, GOOGL, AMZN, NVDA")
744
+ po_period = gr.Dropdown(label="Period", choices=["6mo","1y","2y"], value="1y")
745
+ po_btn = gr.Button("Optimize Portfolio")
746
+ with gr.Column(scale=3):
747
+ po_frontier = gr.Plot()
748
+ po_pie = gr.Plot()
749
+ with gr.Row():
750
+ po_weights = gr.Dataframe()
751
+ po_md = gr.Markdown()
752
+ po_btn.click(fn=optimize_portfolio, inputs=[po_tickers, po_period],
753
+ outputs=[po_frontier, po_pie, po_weights, po_md])
754
+
755
+ with gr.TabItem("Options Pricing"):
756
+ with gr.Row():
757
+ with gr.Column(scale=1):
758
+ op_ticker = gr.Textbox(label="Ticker", value="AAPL")
759
+ op_type = gr.Dropdown(label="Option Type", choices=["call","put"], value="call")
760
+ op_strike = gr.Slider(label="Strike % of Spot", minimum=50, maximum=150, value=100, step=1)
761
+ op_days = gr.Slider(label="Days to Expiry", minimum=7, maximum=365, value=30, step=1)
762
+ op_rfr = gr.Slider(label="Risk-Free Rate %", minimum=0, maximum=10, value=4.5, step=0.1)
763
+ op_vol = gr.Slider(label="Vol Override % (0=auto)", minimum=0, maximum=100, value=0, step=1)
764
+ op_btn = gr.Button("Price Option")
765
+ with gr.Column(scale=3):
766
+ op_greeks = gr.Plot()
767
+ with gr.Row():
768
+ op_scenarios = gr.Dataframe()
769
+ op_md = gr.Markdown()
770
+ op_btn.click(fn=options_pricing, inputs=[op_ticker, op_strike, op_days, op_rfr, op_vol, op_type],
771
+ outputs=[op_greeks, op_scenarios, op_md])
772
+
773
+ with gr.TabItem("Pairs Trading"):
774
+ with gr.Row():
775
+ with gr.Column(scale=1):
776
+ pt_a = gr.Textbox(label="Asset A", value="AAPL")
777
+ pt_b = gr.Textbox(label="Asset B", value="MSFT")
778
+ pt_period = gr.Dropdown(label="Period", choices=["6mo","1y","2y"], value="1y")
779
+ pt_btn = gr.Button("Analyze Pair")
780
+ with gr.Column(scale=3):
781
+ pt_fig = gr.Plot()
782
+ pt_scat = gr.Plot()
783
+ pt_md = gr.Markdown()
784
+ pt_btn.click(fn=pairs_trade, inputs=[pt_a, pt_b, pt_period], outputs=[pt_fig, pt_scat, pt_md])
785
+
786
+ with gr.TabItem("Crypto Arbitrage"):
787
+ with gr.Row():
788
+ with gr.Column(scale=1):
789
+ ca_coins = gr.Textbox(label="Coins (comma-separated)", value="BTC, ETH, SOL, XRP")
790
+ ca_btn = gr.Button("Scan Arbitrage")
791
+ with gr.Column(scale=3):
792
+ ca_heatmap = gr.Plot()
793
+ ca_md = gr.Markdown()
794
+ ca_btn.click(fn=crypto_arbitrage, inputs=ca_coins, outputs=[ca_heatmap, ca_md])
795
+
796
+ with gr.TabItem("Risk Engine"):
797
+ with gr.Row():
798
+ with gr.Column(scale=1):
799
+ re_tickers = gr.Textbox(label="Tickers (comma-separated)", value="AAPL, MSFT, GOOGL, AMZN")
800
+ re_stress = gr.Textbox(label="Stress Shocks JSON", value='{"AAPL":-10, "AMZN":5}', placeholder='{"AAPL":-10, "TSLA":15}')
801
+ re_btn = gr.Button("Run Risk Analysis")
802
+ with gr.Column(scale=3):
803
+ re_corr = gr.Plot()
804
+ re_dist = gr.Plot()
805
+ re_md = gr.Markdown()
806
+ re_btn.click(fn=risk_engine, inputs=[re_tickers, re_stress], outputs=[re_corr, re_dist, re_md])
807
+
808
+ with gr.TabItem("Sentiment"):
809
+ with gr.Row():
810
+ with gr.Column(scale=1):
811
+ se_ticker = gr.Textbox(label="Ticker", value="AAPL")
812
+ se_btn = gr.Button("Analyze Sentiment")
813
+ with gr.Column(scale=3):
814
+ se_gauge = gr.Plot()
815
+ se_md = gr.Markdown()
816
+ se_btn.click(fn=sentiment_analyzer, inputs=se_ticker, outputs=[se_gauge, se_md])
817
+
818
+ with gr.TabItem("Macro Dashboard"):
819
+ with gr.Row():
820
+ ma_btn = gr.Button("Refresh Macro Data")
821
+ ma_fig = gr.Plot()
822
+ ma_md = gr.Markdown()
823
+ ma_btn.click(fn=macro_analysis, inputs=[], outputs=[ma_fig, ma_md])
824
+
825
+ with gr.TabItem("K2 Think V2 Chat"):
826
+ with gr.Row():
827
+ with gr.Column(scale=1):
828
+ k2_prompt = gr.Textbox(label="Ask K2 Think V2", value="Explain the current macro regime and where to allocate capital.", lines=4)
829
+ k2_temp = gr.Slider(label="Temperature", minimum=0.1, maximum=1.0, value=0.3, step=0.1)
830
+ k2_btn = gr.Button("Ask K2")
831
+ with gr.Column(scale=3):
832
+ k2_out = gr.Textbox(label="K2 Response", lines=30)
833
+ k2_btn.click(fn=lambda p,t: K2ThinkClient().chat([{"role":"user","content":p}], temperature=t, max_tokens=4096),
834
+ inputs=[k2_prompt, k2_temp], outputs=k2_out)
835
+
836
+ with gr.TabItem("About"):
837
+ gr.Markdown("""
838
+ ## AlphaForge V3.1
839
+
840
+ **Built for the Build with K2 Think V2 Challenge by MBZUAI**
841
+
842
+ ### 10 Quant Modules
843
+ | Module | Feature | Institution |
844
+ |--------|---------|-------------|
845
+ | Technical | 18+ indicators, candlestick, Ichimoku, VWAP, ADX, MFI, OBV | Bloomberg Terminal |
846
+ | AI Analysis | K2 Think V2 chain-of-thought with 7-section structured output | MBZUAI K2 |
847
+ | Backtest | 5 strategies, ATR sizing, equity curves, drawdown | Jane Street |
848
+ | Portfolio | Markowitz MPT, 10K-portfolio MC, efficient frontier | AQR, D.E. Shaw |
849
+ | Options | Black-Scholes + full Greeks (DGThVR), scenario P/L | Goldman Sachs |
850
+ | Pairs | Cointegration, OLS hedge ratio, OU half-life, Z-score | Two Sigma |
851
+ | Crypto Arb | Cross-exchange spread heatmap, funding rate concepts | Jump Trading |
852
+ | Risk Engine | VaR 95/99, stress testing, correlation breakdown | Bridgewater |
853
+ | Sentiment | Composite gauge (-100 to +100), keyword extraction | Citadel NLP |
854
+ | Macro | Cross-asset regime dashboard (S&P, 10Y, Gold, Oil, DXY, BTC) | Bridgewater All Weather |
855
+
856
+ ### K2 Think V2 API Key Setup
857
+ 1. Go to Space Settings > Repository Secrets
858
+ 2. Add secret: `K2_API_KEY`
859
+ 3. No key required - all quant modules work standalone
860
+
861
+ ### Data Notice
862
+ When Yahoo Finance rate-limits shared IPs (common on HF Spaces), the app falls back to **deterministic synthetic data** seeded by ticker + date. Same ticker = same data on the same day.
863
+
864
+ ### Stack
865
+ - yfinance / synthetic fallback
866
+ - Plotly (Bloomberg Terminal aesthetic)
867
+ - NumPy/Pandas (vectorized quant math)
868
+ - K2 Think V2 (MBZUAI reasoning)
869
+
870
+ ---
871
+ *Built by Premchan | Build with K2 Think V2*
872
+ """)
873
+
874
+ return app
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
875
 
876
  if __name__ == "__main__":
877
+ build_app().launch()