Spaces:
Running
Running
| """AlphaForge x K2 Think V2 - Institutional-Grade Quantitative Analysis Platform | |
| Multi-market support: US, EU, Asia, Crypto, Forex, Commodities | |
| Finance-themed UI with dark mode, professional color scheme | |
| Enhanced features: Options pricing, Pairs Trading, Macro Analysis | |
| Powered by MBZUAI K2 Think V2 reasoning model | |
| API Key: set via K2_API_KEY environment variable | |
| """ | |
| import os, json, traceback, warnings, math, random | |
| warnings.filterwarnings('ignore') | |
| # Core imports | |
| try: | |
| import gradio as gr | |
| import requests | |
| import yfinance as yf | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| PLOTLY_OK = True | |
| except ImportError as e: | |
| raise ImportError(f"Missing required package: {e}") | |
| # CONFIG | |
| K2_API_KEY = os.environ.get("K2_API_KEY", "") | |
| K2_BASE_URL = "https://api.k2think.ai/v1/chat/completions" | |
| K2_MODEL = "MBZUAI-IFM/K2-Think-v2" | |
| # K2 THINK V2 CLIENT | |
| class K2ThinkClient: | |
| def __init__(self): | |
| self.api_key = K2_API_KEY | |
| self.available = bool(self.api_key) and len(self.api_key) > 10 | |
| self.base_url = K2_BASE_URL | |
| def chat(self, messages, temperature=0.7, max_tokens=4096): | |
| if not self.available: | |
| return "⚠️ K2 Think V2 API Not Configured. Add K2_API_KEY in Space Settings > Repository Secrets. All other features work perfectly!" | |
| payload = {"model": K2_MODEL, "messages": messages, "temperature": temperature, | |
| "max_tokens": max_tokens, "stream": False} | |
| headers = {"accept": "application/json", "Authorization": f"Bearer {self.api_key}", | |
| "Content-Type": "application/json"} | |
| try: | |
| r = requests.post(self.base_url, headers=headers, json=payload, timeout=120) | |
| r.raise_for_status() | |
| j = r.json() | |
| if 'choices' in j and len(j['choices']) > 0: | |
| return j['choices'][0]['message']['content'] | |
| return f"⚠️ Unexpected format: {json.dumps(j, indent=2)[:400]}" | |
| except requests.exceptions.Timeout: | |
| return "⏱️ Timeout after 120s. API may be under high load." | |
| except requests.exceptions.HTTPError as e: | |
| if e.response.status_code == 401: | |
| return "🔐 Auth failed. Check K2_API_KEY secret." | |
| elif e.response.status_code == 429: | |
| return "🚦 Rate limited. Wait a moment." | |
| return f"🔴 HTTP {e.response.status_code}: {str(e)[:200]}" | |
| except Exception as e: | |
| return f"🔴 Error: {str(e)[:300]}" | |
| def analyze_market(self, ticker, market, data_summary, tech_summary, timeframe): | |
| prompt = f"""You are an elite quantitative analyst at a top hedge fund (Two Sigma / Jane Street level). | |
| Analyze with deep chain-of-thought reasoning. | |
| ## Asset Information | |
| - **Ticker**: {ticker} | |
| - **Market**: {market} | |
| - **Timeframe**: {timeframe} | |
| ## Market Data Summary | |
| {data_summary} | |
| ## Technical Indicators | |
| {tech_summary} | |
| ## Deliverables | |
| Provide exactly these sections: | |
| ### 1. Executive Summary (3 bullets) | |
| ### 2. Technical Analysis | |
| - Interpret RSI, MACD, Bollinger Bands, ADX, Ichimoku | |
| - Identify support/resistance levels from SMAs and VWAP | |
| ### 3. Risk Assessment | |
| - Volatility regime (low/normal/high) | |
| - Tail risk estimate | |
| - Correlation risk | |
| ### 4. Alpha Signal | |
| - Direction: BULLISH / NEUTRAL / BEARISH | |
| - Confidence: X% | |
| - Time horizon | |
| - Key conviction drivers | |
| ### 5. Trade Recommendation | |
| - Entry price / zone | |
| - Stop-loss | |
| - Target 1 (conservative) and Target 2 (aggressive) | |
| - Position sizing suggestion | |
| ### 6. Catalyst Calendar | |
| - Next 7 days | |
| - Next 30 days | |
| ### 7. Contrarian View | |
| - What would make this signal wrong? | |
| - Alternative scenario with probability | |
| Think step-by-step. Reference specific numbers.""" | |
| return self.chat([{"role": "user", "content": prompt}], temperature=0.2, max_tokens=4096) | |
| def portfolio_advice(self, portfolio_data, corr_data, risk_metrics, market_context): | |
| prompt = f"""You are a CIO managing $2B AUM at a systematic macro fund. | |
| ## Portfolio Holdings | |
| {portfolio_data} | |
| ## Correlation Analysis | |
| {corr_data} | |
| ## Risk Metrics | |
| {risk_metrics} | |
| ## Market Context | |
| {market_context} | |
| ## Deliverables | |
| ### 1. Portfolio Health Score (0-100) with letter grade (A+ to F) | |
| ### 2. Concentration Risk | |
| ### 3. Correlation Risk Matrix | |
| ### 4. Rebalancing Roadmap | |
| - Specific weight adjustments with % | |
| - Timeline: immediate / 1 week / 1 month | |
| ### 5. Hedging Strategy | |
| ### 6. Expected Return & Risk (Forward 12M) | |
| ### 7. Scenario Analysis | |
| - Bull case (20% probability) | |
| - Base case (50% probability) | |
| - Bear case (20% probability) | |
| - Tail case (10% probability) | |
| Use quantitative reasoning throughout.""" | |
| return self.chat([{"role": "user", "content": prompt}], temperature=0.2, max_tokens=4096) | |
| def macro_analysis(self, macro_text): | |
| prompt = f"""You are a global macro strategist at Bridgewater / Millennium. | |
| ## Input | |
| {macro_text} | |
| ## Deliverables | |
| ### 1. Macro Regime Classification | |
| ### 2. Cross-Asset Implications | |
| - Equities, Fixed Income, FX, Commodities, Crypto | |
| ### 3. Trade Ideas (3 concrete setups) | |
| Each with: instrument, direction, entry, stop, target, conviction %, time horizon | |
| ### 4. Risk Factors | |
| Think like a macro PM.""" | |
| return self.chat([{"role": "user", "content": prompt}], temperature=0.3, max_tokens=4096) | |
| # MARKET DATA | |
| MARKET_PRESETS = { | |
| "🇺🇸 US Equities": {"suffix": "", "examples": "AAPL, TSLA, NVDA, SPY, QQQ, META, AMZN, GOOGL"}, | |
| "🇪🇺 European Equities": {"suffix": ".PA", "examples": "AIR.PA, SAN.PA, TTE.PA, OR.PA, MC.PA"}, | |
| "🇬🇧 UK Equities": {"suffix": ".L", "examples": "AZN.L, SHEL.L, BP.L, ULVR.L, RIO.L"}, | |
| "🇩🇪 German Equities": {"suffix": ".DE", "examples": "SAP.DE, SIE.DE, ALV.DE, BAS.DE, BMW.DE"}, | |
| "🇯🇵 Japanese Equities": {"suffix": ".T", "examples": "7203.T, 9984.T, 6861.T, 6758.T"}, | |
| "🇨🇳 Chinese Equities": {"suffix": ".HK", "examples": "0700.HK, 9988.HK, 3690.HK, 1810.HK"}, | |
| "🇮🇳 Indian Equities": {"suffix": ".NS", "examples": "RELIANCE.NS, TCS.NS, INFY.NS"}, | |
| "🪙 Crypto": {"suffix": "", "examples": "BTC-USD, ETH-USD, SOL-USD, XRP-USD"}, | |
| "💱 Forex Majors": {"suffix": "=X", "examples": "EURUSD=X, GBPUSD=X, USDJPY=X"}, | |
| "🥇 Commodities": {"suffix": "", "examples": "GC=F, SI=F, CL=F, NG=F, ZC=F"}, | |
| "📊 Indices": {"suffix": "", "examples": "^GSPC, ^DJI, ^IXIC, ^FTSE, ^N225"}, | |
| } | |
| def fetch_data(ticker, period="6mo", interval="1d"): | |
| try: | |
| stock = yf.Ticker(ticker.upper().strip()) | |
| df = stock.history(period=period, interval=interval) | |
| if df.empty: | |
| return None, None, f"No data for '{ticker}'. Try examples from the selected market." | |
| info = stock.info | |
| return df, info, None | |
| except Exception as e: | |
| return None, None, f"Error fetching '{ticker}': {str(e)[:200]}" | |
| def calc_indicators(df): | |
| df = df.copy() | |
| df['Ret'] = df['Close'].pct_change() | |
| df['LogRet'] = np.log(df['Close']/df['Close'].shift(1)) | |
| df['SMA5'] = df['Close'].rolling(5).mean() | |
| df['SMA20'] = df['Close'].rolling(20).mean() | |
| df['SMA50'] = df['Close'].rolling(50).mean() | |
| df['SMA200'] = df['Close'].rolling(200).mean() | |
| df['EMA12'] = df['Close'].ewm(span=12, adjust=False).mean() | |
| df['EMA26'] = df['Close'].ewm(span=26, adjust=False).mean() | |
| df['MACD'] = df['EMA12'] - df['EMA26'] | |
| df['MACDS'] = df['MACD'].ewm(span=9, adjust=False).mean() | |
| df['MACDH'] = df['MACD'] - df['MACDS'] | |
| d = df['Close'].diff() | |
| g = d.where(d>0,0).rolling(14).mean() | |
| l = (-d.where(d<0,0)).rolling(14).mean() | |
| df['RSI'] = 100 - (100/(1+g/(l+1e-10))) | |
| m = df['Close'].rolling(20).mean() | |
| s = df['Close'].rolling(20).std() | |
| df['BBU'] = m + 2*s | |
| df['BBL'] = m - 2*s | |
| df['BBP'] = (df['Close']-df['BBL'])/(df['BBU']-df['BBL']+1e-10) | |
| df['BBW'] = (df['BBU']-df['BBL'])/m | |
| tp = (df['High']+df['Low']+df['Close'])/3 | |
| df['VWAP'] = (tp*df['Volume']).cumsum()/(df['Volume'].cumsum()+1e-10) | |
| hl = df['High']-df['Low'] | |
| hc = np.abs(df['High']-df['Close'].shift()) | |
| lc = np.abs(df['Low']-df['Close'].shift()) | |
| tr = pd.concat([hl,hc,lc],axis=1).max(axis=1) | |
| df['ATR'] = tr.rolling(14).mean() | |
| df['ATR_pct'] = df['ATR']/df['Close']*100 | |
| lo = df['Low'].rolling(14).min() | |
| hi = df['High'].rolling(14).max() | |
| df['Stoch_K'] = 100*(df['Close']-lo)/(hi-lo+1e-10) | |
| df['Stoch_D'] = df['Stoch_K'].rolling(3).mean() | |
| df['VM'] = df['Volume'].rolling(20).mean() | |
| df['VR'] = df['Volume']/(df['VM']+1e-10) | |
| plus_dm = df['High'].diff() | |
| minus_dm = df['Low'].diff() | |
| plus_dm[plus_dm<0] = 0 | |
| minus_dm[minus_dm>0] = 0 | |
| minus_dm = np.abs(minus_dm) | |
| atr_smooth = tr.ewm(alpha=1/14, adjust=False).mean() | |
| df['plus_DI'] = 100 * (plus_dm.ewm(alpha=1/14, adjust=False).mean() / atr_smooth) | |
| df['minus_DI'] = 100 * (minus_dm.ewm(alpha=1/14, adjust=False).mean() / atr_smooth) | |
| dx = 100 * np.abs(df['plus_DI']-df['minus_DI'])/(df['plus_DI']+df['minus_DI']+1e-10) | |
| df['ADX'] = dx.ewm(alpha=1/14, adjust=False).mean() | |
| df['OBV'] = (np.sign(df['Close'].diff())*df['Volume']).cumsum() | |
| tp_r = (df['High']+df['Low']+df['Close'])/3 | |
| tp_diff = tp_r.diff() | |
| pos_flow = tp_r.where(tp_diff>0,0)*df['Volume'] | |
| neg_flow = tp_r.where(tp_diff<0,0)*df['Volume'] | |
| mfi_pos = pos_flow.rolling(14).sum() | |
| mfi_neg = neg_flow.rolling(14).sum() | |
| df['MFI'] = 100 - (100/(1+mfi_pos/(mfi_neg+1e-10))) | |
| df['ICH_tenkan'] = (df['High'].rolling(9).max()+df['Low'].rolling(9).min())/2 | |
| df['ICH_kijun'] = (df['High'].rolling(26).max()+df['Low'].rolling(26).min())/2 | |
| df['ICH_senkou_A'] = ((df['ICH_tenkan']+df['ICH_kijun'])/2).shift(26) | |
| df['ICH_senkou_B'] = ((df['High'].rolling(52).max()+df['Low'].rolling(52).min())/2).shift(26) | |
| return df | |
| def calc_risk(df): | |
| r = df['Ret'].dropna() | |
| if len(r) < 30: | |
| return {} | |
| ar = r.mean()*252 | |
| av = r.std()*np.sqrt(252) | |
| sh = ar/(av+1e-10) | |
| dn = r[r<0] | |
| sd = dn.std()*np.sqrt(252) if len(dn)>0 else 1e-10 | |
| so = ar/(sd+1e-10) | |
| c = (1+r).cumprod() | |
| rm = c.expanding().max() | |
| md = ((c-rm)/rm).min() | |
| v95 = np.percentile(r,5) | |
| v99 = np.percentile(r,1) | |
| cv95 = r[r<=v95].mean() if len(r[r<=v95])>0 else v95 | |
| cv99 = r[r<=v99].mean() if len(r[r<=v99])>0 else v99 | |
| ca = ar/(abs(md)+1e-10) | |
| roll_sharpe = (r.rolling(63).mean()*252)/(r.rolling(63).std()*np.sqrt(252)+1e-10) | |
| return {'ar':ar,'av':av,'sh':sh,'so':so,'md':md,'v95':v95,'v99':v99, | |
| 'cv95':cv95,'cv99':cv99,'ca':ca,'sk':r.skew(),'ku':r.kurtosis(), | |
| 'wr':(r>0).mean(),'pf':abs(r[r>0].sum()/(r[r<0].sum()+1e-10)), | |
| 'avg_win':r[r>0].mean() if len(r[r>0])>0 else 0, | |
| 'avg_loss':r[r<0].mean() if len(r[r<0])>0 else 0, | |
| 'roll_sharpe':roll_sharpe.iloc[-1] if len(roll_sharpe.dropna())>0 else 0, | |
| 'vol_regime':'low' if av<0.15 else 'normal' if av<0.30 else 'high'} | |
| def calc_signals(df): | |
| l = df.iloc[-1] | |
| p = df.iloc[-2] if len(df)>1 else l | |
| s = {'trend':'neutral','mom':'neutral','vol':'normal','volume':'normal', | |
| 'adx_trend':'weak','score':50,'ichimoku':'neutral'} | |
| if l['Close']>l['SMA20']>l['SMA50']>l['SMA200']: | |
| s['trend'] = 'strongly bullish' | |
| elif l['Close']>l['SMA20']>l['SMA50']: | |
| s['trend'] = 'bullish' | |
| elif l['Close']<l['SMA20']<l['SMA50']<l['SMA200']: | |
| s['trend'] = 'strongly bearish' | |
| elif l['Close']<l['SMA20']<l['SMA50']: | |
| s['trend'] = 'bearish' | |
| if l['RSI']<30: | |
| s['mom'] = 'deeply oversold' | |
| elif l['RSI']<40: | |
| s['mom'] = 'oversold' | |
| elif l['RSI']>70: | |
| s['mom'] = 'deeply overbought' | |
| elif l['RSI']>60: | |
| s['mom'] = 'overbought' | |
| elif l['MACD']>l['MACDS'] and p['MACD']<=p['MACDS']: | |
| s['mom'] = 'bullish MACD crossover' | |
| elif l['MACD']<l['MACDS'] and p['MACD']>=p['MACDS']: | |
| s['mom'] = 'bearish MACD crossover' | |
| if l['BBW'] > df['BBW'].quantile(0.9): | |
| s['vol'] = 'expanding (high vol)' | |
| elif l['BBW'] < df['BBW'].quantile(0.1): | |
| s['vol'] = 'contracting (low vol / squeeze)' | |
| if l['VR'] > 2.5: | |
| s['volume'] = 'very heavy (institutional)' | |
| elif l['VR'] > 1.5: | |
| s['volume'] = 'above average' | |
| if l['ADX'] > 25: | |
| s['adx_trend'] = 'strong trend' | |
| elif l['ADX'] > 20: | |
| s['adx_trend'] = 'trending' | |
| if l['Close'] > l['ICH_senkou_A'] and l['Close'] > l['ICH_senkou_B']: | |
| s['ichimoku'] = 'bullish cloud' | |
| elif l['Close'] < l['ICH_senkou_A'] and l['Close'] < l['ICH_senkou_B']: | |
| s['ichimoku'] = 'bearish cloud' | |
| sc = 50 | |
| if 'bullish' in s['trend']: sc += 20 | |
| if 'bearish' in s['trend']: sc -= 20 | |
| if 'oversold' in s['mom']: sc += 10 | |
| if 'overbought' in s['mom']: sc -= 10 | |
| if 'crossover' in s['mom'] and 'bullish' in s['mom']: sc += 10 | |
| if 'crossover' in s['mom'] and 'bearish' in s['mom']: sc -= 10 | |
| if l['Close'] > l['VWAP']: sc += 5 | |
| if l['Close'] < l['VWAP']: sc -= 5 | |
| if l['Stoch_K'] < 20: sc += 5 | |
| if l['Stoch_K'] > 80: sc -= 5 | |
| if s['ichimoku'] == 'bullish cloud': sc += 5 | |
| if s['ichimoku'] == 'bearish cloud': sc -= 5 | |
| s['score'] = max(0, min(100, sc)) | |
| s['dir'] = 'BULLISH' if sc>60 else 'BEARISH' if sc<40 else 'NEUTRAL' | |
| s['strength'] = 'STRONG' if abs(sc-50)>25 else 'MODERATE' if abs(sc-50)>15 else 'WEAK' | |
| return s | |
| def make_candlestick(df, ticker, market): | |
| fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.03, | |
| row_heights=[0.55, 0.25, 0.20], | |
| subplot_titles=(f'{ticker} ({market})', 'Volume + VWAP', 'RSI')) | |
| fig.add_trace(go.Candlestick(x=df.index, open=df['Open'], high=df['High'], | |
| low=df['Low'], close=df['Close'], name='Price', | |
| increasing_line_color='#00C853', decreasing_line_color='#FF5252'), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['SMA20'], line=dict(color='#FF9800', width=1), name='SMA20'), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['SMA50'], line=dict(color='#2196F3', width=1), name='SMA50'), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['SMA200'], line=dict(color='#9C27B0', width=1.5, dash='dash'), name='SMA200'), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['BBU'], line=dict(color='gray', width=0.8, dash='dash'), name='BB+', opacity=0.4), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['BBL'], line=dict(color='gray', width=0.8, dash='dash'), name='BB-', opacity=0.4), row=1, col=1) | |
| colors = ['#00C853' if df['Close'].iloc[i]>=df['Open'].iloc[i] else '#FF5252' for i in range(len(df))] | |
| fig.add_trace(go.Bar(x=df.index, y=df['Volume'], marker_color=colors, name='Volume', opacity=0.7), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['VM'], line=dict(color='#FF9800', width=1), name='Vol MA20'), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['RSI'], line=dict(color='#9C27B0', width=1.5), fill='tozeroy', fillcolor='rgba(156,39,176,0.1)'), row=3, col=1) | |
| fig.add_hline(y=70, line_dash="dash", line_color="#FF5252", row=3, col=1) | |
| fig.add_hline(y=30, line_dash="dash", line_color="#00C853", row=3, col=1) | |
| fig.add_hline(y=50, line_dash="dot", line_color="gray", row=3, col=1) | |
| fig.update_layout(title=f'{ticker} Technical Analysis', template='plotly_dark', | |
| xaxis_rangeslider_visible=False, height=850, | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', | |
| font=dict(color='#e6edf3')) | |
| fig.update_yaxes(title_text="Price", row=1, col=1) | |
| fig.update_yaxes(title_text="Volume", row=2, col=1) | |
| fig.update_yaxes(title_text="RSI", range=[0,100], row=3, col=1) | |
| return fig | |
| def make_macd(df, ticker): | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, | |
| row_heights=[0.6, 0.4], subplot_titles=('MACD', 'Histogram')) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['MACD'], line=dict(color='#2196F3', width=1.5), name='MACD'), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['MACDS'], line=dict(color='#FF9800', width=1.5), name='Signal'), row=1, col=1) | |
| colors = ['#00C853' if v>=0 else '#FF5252' for v in df['MACDH']] | |
| fig.add_trace(go.Bar(x=df.index, y=df['MACDH'], marker_color=colors, name='Histogram', opacity=0.7), row=2, col=1) | |
| fig.update_layout(title=f'{ticker} MACD', template='plotly_dark', height=500, | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| return fig | |
| def make_stoch(df, ticker): | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter(x=df.index, y=df['Stoch_K'], line=dict(color='#2196F3', width=1.5), name='%K')) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['Stoch_D'], line=dict(color='#FF9800', width=1.5), name='%D')) | |
| fig.add_hline(y=80, line_dash="dash", line_color="#FF5252") | |
| fig.add_hline(y=20, line_dash="dash", line_color="#00C853") | |
| fig.update_layout(title=f'{ticker} Stochastic', template='plotly_dark', height=400, | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| return fig | |
| def make_vol(df, ticker): | |
| fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.05, | |
| row_heights=[0.6, 0.4], subplot_titles=('ATR %', 'Volume Ratio')) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['ATR_pct'], line=dict(color='#FF9800', width=1.5), fill='tozeroy'), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['VR'], line=dict(color='#9C27B0', width=1.5)), row=2, col=1) | |
| fig.add_hline(y=1.0, line_dash="dash", line_color="gray", row=2, col=1) | |
| fig.update_layout(title=f'{ticker} Volatility', template='plotly_dark', height=500, | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| return fig | |
| def make_adx(df, ticker): | |
| fig = go.Figure() | |
| fig.add_trace(go.Scatter(x=df.index, y=df['plus_DI'], line=dict(color='#00C853', width=1), name='+DI')) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['minus_DI'], line=dict(color='#FF5252', width=1), name='-DI')) | |
| fig.add_trace(go.Scatter(x=df.index, y=df['ADX'], line=dict(color='#2196F3', width=2), name='ADX')) | |
| fig.add_hline(y=25, line_dash="dash", line_color="gray") | |
| fig.update_layout(title=f'{ticker} ADX', template='plotly_dark', height=400, | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| return fig | |
| def make_dist(r, ticker): | |
| fig = go.Figure() | |
| fig.add_trace(go.Histogram(x=r, nbinsx=50, marker_color='#2196F3', opacity=0.7, name='Returns')) | |
| mu, sig = r.mean(), r.std() | |
| fig.add_vline(x=mu, line_color='#00C853', line_dash='dash', annotation_text=f'Mean: {mu*100:.2f}%') | |
| fig.add_vline(x=np.percentile(r,5), line_color='#FF5252', line_dash='dot', annotation_text='VaR95') | |
| fig.update_layout(title=f'{ticker} Returns', xaxis_title='Daily Return', yaxis_title='Count', | |
| height=400, template='plotly_dark', | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| return fig | |
| # PORTFOLIO | |
| def optimize_portfolio(tickers, period="1y"): | |
| ts = [t.strip().upper() for t in tickers.split(',') if t.strip()] | |
| if len(ts) < 2: | |
| return None, None, None, "Enter at least 2 tickers." | |
| data = {} | |
| errs = [] | |
| for t in ts: | |
| df, info, err = fetch_data(t, period) | |
| if err: | |
| errs.append(err) | |
| elif df is not None and len(df) > 30: | |
| data[t] = df['Close'] | |
| if len(data) < 2: | |
| return None, None, None, f"Could not fetch data: {'; '.join(errs[:3])}" | |
| prices = pd.DataFrame(data).dropna() | |
| returns = prices.pct_change().dropna() | |
| if len(returns) < 30: | |
| return None, None, None, "Need more data." | |
| mu = returns.mean() * 252 | |
| sigma = returns.cov() * 252 | |
| n = len(mu) | |
| best_sh = -999 | |
| best_w = np.ones(n)/n | |
| np.random.seed(42) | |
| for _ in range(5000): | |
| w = np.random.dirichlet(np.ones(n)*0.5) | |
| w = np.clip(w, 0, 0.4) | |
| w = w/w.sum() | |
| pr = np.dot(w, mu) | |
| pv = np.sqrt(np.dot(w.T, np.dot(sigma, w))) | |
| sh = pr/(pv+1e-10) | |
| if sh > best_sh: | |
| best_sh = sh | |
| best_w = w | |
| pr = np.dot(best_w, mu) | |
| pv = np.sqrt(np.dot(best_w.T, np.dot(sigma, best_w))) | |
| eq_w = np.ones(n)/n | |
| eq_r = np.dot(eq_w, mu) | |
| eq_v = np.sqrt(np.dot(eq_w.T, np.dot(sigma, eq_w))) | |
| ws = np.random.dirichlet(np.ones(n)*0.5, 3000) | |
| ws = np.clip(ws, 0, 0.4) | |
| ws = ws/ws.sum(axis=1, keepdims=True) | |
| prets = np.dot(ws, mu) | |
| pvols = np.array([np.sqrt(np.dot(w.T, np.dot(sigma, w))) for w in ws]) | |
| psh = prets/(pvols+1e-10) | |
| fig = go.Figure() | |
| 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')) | |
| fig.add_trace(go.Scatter(x=[pv], y=[pr], mode='markers+text', | |
| marker=dict(size=18, color='#FF5252', symbol='star'), | |
| text=['Optimal'], textposition='top center', name='Optimal')) | |
| fig.add_trace(go.Scatter(x=[eq_v], y=[eq_r], mode='markers+text', | |
| marker=dict(size=14, color='#FF9800', symbol='diamond'), | |
| text=['Equal'], textposition='bottom center', name='Equal Weight')) | |
| fig.update_layout(title='Efficient Frontier', xaxis_title='Volatility', yaxis_title='Return', | |
| template='plotly_dark', height=550, | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| corr = returns.corr() | |
| corr_fig = 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'))) | |
| corr_fig.update_layout(title='Correlation Matrix', template='plotly_dark', height=450, | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| wdf = pd.DataFrame({'Ticker': list(data.keys()), | |
| 'Optimal (%)': np.round(best_w*100, 2), | |
| 'Equal (%)': np.round(eq_w*100, 2)}) | |
| md = f"""## Portfolio Results | |
| **Tickers:** {', '.join(list(data.keys()))} | |
| | | Optimal | Equal | | |
| |-|---------|-------| | |
| | Return | {pr*100:.1f}% | {eq_r*100:.1f}% | | |
| | Volatility | {pv*100:.1f}% | {eq_v*100:.1f}% | | |
| | Sharpe | {best_sh:.2f} | {eq_r/(eq_v+1e-10):.2f} | | |
| Improvements: Sharpe {((best_sh/(eq_r/(eq_v+1e-10))-1)*100):+.1f}% | |
| {wdf.to_markdown(index=False)} | |
| """ | |
| return fig, corr_fig, wdf, md | |
| # PAIRS TRADING | |
| def analyze_pair(ticker_a, ticker_b, period="1y"): | |
| df_a, _, _ = fetch_data(ticker_a, period) | |
| df_b, _, _ = fetch_data(ticker_b, period) | |
| if df_a is None or df_b is None: | |
| return None, None, "Could not fetch data for one or both tickers." | |
| prices = pd.DataFrame({ticker_a: df_a['Close'], ticker_b: df_b['Close']}).dropna() | |
| if len(prices) < 30: | |
| return None, None, "Insufficient aligned data." | |
| spread = prices[ticker_a] - prices[ticker_b] | |
| spread_norm = (spread - spread.mean()) / spread.std() | |
| beta = np.polyfit(prices[ticker_b], prices[ticker_a], 1)[0] | |
| hedge_ratio = beta | |
| spread_hedged = prices[ticker_a] - hedge_ratio * prices[ticker_b] | |
| spread_hedged_norm = (spread_hedged - spread_hedged.mean()) / spread_hedged.std() | |
| lag_spread = spread_hedged.shift(1) | |
| delta_spread = spread_hedged.diff() | |
| valid = delta_spread.dropna().index | |
| y = delta_spread.loc[valid] | |
| x = lag_spread.loc[valid] - spread_hedged.mean() | |
| theta = -np.polyfit(x, y, 1)[0] | |
| half_life = np.log(2)/theta if theta > 0 else float('inf') | |
| z = spread_hedged_norm.iloc[-1] | |
| signal = 'SHORT SPREAD' if z > 2 else 'LONG SPREAD' if z < -2 else 'NO SIGNAL' | |
| fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.05, | |
| subplot_titles=(f'{ticker_a} vs {ticker_b}', 'Normalized Spread', 'Z-Score')) | |
| fig.add_trace(go.Scatter(x=prices.index, y=prices[ticker_a], line=dict(color='#2196F3', width=1.5), name=ticker_a), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=prices.index, y=prices[ticker_b], line=dict(color='#FF9800', width=1.5), name=ticker_b), row=1, col=1) | |
| fig.add_trace(go.Scatter(x=prices.index, y=spread_norm, line=dict(color='#00C853', width=1.5), fill='tozeroy'), row=2, col=1) | |
| fig.add_trace(go.Scatter(x=prices.index, y=spread_hedged_norm, line=dict(color='#9C27B0', width=1.5)), row=3, col=1) | |
| fig.add_hline(y=2, line_dash="dash", line_color="#FF5252", row=3, col=1) | |
| fig.add_hline(y=-2, line_dash="dash", line_color="#00C853", row=3, col=1) | |
| fig.add_hline(y=0, line_dash="dot", line_color="gray", row=3, col=1) | |
| fig.update_layout(title=f'Pairs: {ticker_a} / {ticker_b}', template='plotly_dark', | |
| height=750, paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| scat = go.Figure() | |
| scat.add_trace(go.Scatter(x=prices[ticker_b], y=prices[ticker_a], mode='markers', | |
| marker=dict(size=4, color=np.arange(len(prices)), colorscale='Viridis', showscale=True), | |
| name='Price Path')) | |
| x_range = np.linspace(prices[ticker_b].min(), prices[ticker_b].max(), 100) | |
| intercept = np.polyfit(prices[ticker_b], prices[ticker_a], 1)[1] | |
| y_range = hedge_ratio * x_range + intercept | |
| scat.add_trace(go.Scatter(x=x_range, y=y_range, mode='lines', | |
| line=dict(color='#FF5252', dash='dash'), name=f'OLS (β={hedge_ratio:.2f})')) | |
| scat.update_layout(title=f'Price Relationship (β={hedge_ratio:.2f})', template='plotly_dark', | |
| xaxis_title=ticker_b, yaxis_title=ticker_a, height=450, | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| md = f"""## Pairs Trading: {ticker_a} vs {ticker_b} | |
| | Metric | Value | | |
| |--------|-------| | |
| | Hedge Ratio (β) | {hedge_ratio:.3f} | | |
| | Spread Mean | ${spread_hedged.mean():.2f} | | |
| | Spread Std | ${spread_hedged.std():.2f} | | |
| | Current Z-Score | {z:.2f} | | |
| | Half-Life | {half_life:.1f} days | | |
| ### Signal | |
| | Z-Score | Action | | |
| |---------|--------| | |
| | {z:.2f} | **{signal}** | | |
| ### Rules | |
| - **Long Spread** when Z < -2 (buy {ticker_a}, short {ticker_b}) | |
| - **Short Spread** when Z > +2 (short {ticker_a}, buy {ticker_b}) | |
| - **Exit** when Z crosses 0 | |
| - **Stop Loss** when |Z| > 3.5 | |
| """ | |
| return fig, scat, md | |
| # OPTIONS | |
| def black_scholes(S, K, T, r, sigma, option_type='call'): | |
| try: | |
| d1 = (np.log(S/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T)) | |
| d2 = d1 - sigma*np.sqrt(T) | |
| try: | |
| from scipy.stats import norm | |
| nd1 = norm.cdf(d1) | |
| nd2 = norm.cdf(d2) | |
| npdf_d1 = norm.pdf(d1) | |
| except: | |
| def approx_cdf(x): | |
| return 0.5 * (1 + math.erf(x / math.sqrt(2))) | |
| nd1 = approx_cdf(d1) | |
| nd2 = approx_cdf(d2) | |
| npdf_d1 = (1/math.sqrt(2*math.pi)) * math.exp(-0.5*d1**2) | |
| if option_type == 'call': | |
| price = S*nd1 - K*math.exp(-r*T)*nd2 | |
| delta = nd1 | |
| else: | |
| price = K*math.exp(-r*T)*(1-nd2) - S*(1-nd1) | |
| delta = nd1 - 1 | |
| gamma = npdf_d1 / (S*sigma*np.sqrt(T)) | |
| theta = -(S*npdf_d1*sigma)/(2*np.sqrt(T)) - r*K*math.exp(-r*T)*nd2 if option_type=='call' else -(S*npdf_d1*sigma)/(2*np.sqrt(T)) + r*K*math.exp(-r*T)*(1-nd2) | |
| vega = S*npdf_d1*np.sqrt(T) | |
| rho = K*T*math.exp(-r*T)*nd2 if option_type=='call' else -K*T*math.exp(-r*T)*(1-nd2) | |
| return {'price': price, 'delta': delta, 'gamma': gamma, 'theta': theta/252, | |
| 'vega': vega/100, 'rho': rho/100, 'd1': d1, 'd2': d2} | |
| except Exception as e: | |
| return {'error': str(e)} | |
| def analyze_options(ticker, strike_pct, days, rfr, vol_override, option_type): | |
| df, info, err = fetch_data(ticker, "6mo") | |
| if df is None: | |
| return None, None, f"Error: {err}" | |
| df = calc_indicators(df) | |
| S = df['Close'].iloc[-1] | |
| K = S * (strike_pct/100) | |
| T = days / 365 | |
| if vol_override and vol_override > 0: | |
| sigma = vol_override / 100 | |
| else: | |
| sigma = df['Ret'].dropna().std() * np.sqrt(252) | |
| r = rfr / 100 | |
| bs = black_scholes(S, K, T, r, sigma, option_type.lower()) | |
| if 'error' in bs: | |
| return None, None, f"BS Error: {bs['error']}" | |
| pct_changes = np.arange(-30, 31, 5) | |
| pl_data = [] | |
| for pct in pct_changes: | |
| new_S = S * (1 + pct/100) | |
| new_bs = black_scholes(new_S, K, max(T - 1/365, 0.001), r, sigma, option_type.lower()) | |
| pl = (new_bs['price'] - bs['price']) * 100 | |
| pl_data.append({'Price Change %': f'{pct:+d}%', 'Stock Price': f'${new_S:.2f}', | |
| 'Option Price': f'${new_bs["price"]:.2f}', 'P/L (per 100)': f'${pl:+.2f}'}) | |
| pl_df = pd.DataFrame(pl_data) | |
| strikes = np.linspace(S*0.7, S*1.3, 50) | |
| greeks_data = {'price': [], 'delta': [], 'gamma': [], 'theta': [], 'vega': []} | |
| for st in strikes: | |
| res = black_scholes(S, st, T, r, sigma, option_type.lower()) | |
| for k in greeks_data: | |
| greeks_data[k].append(res.get(k, 0)) | |
| fig = make_subplots(rows=2, cols=3, | |
| subplot_titles=('Price', 'Delta', 'Gamma', 'Theta (daily)', 'Vega', 'P/L at Expiry'), | |
| vertical_spacing=0.12, horizontal_spacing=0.08) | |
| colors = ['#2196F3', '#00C853', '#FF9800', '#FF5252', '#9C27B0', '#673AB7'] | |
| for i, (k, v) in enumerate(greeks_data.items()): | |
| row, col = (i//3)+1, (i%3)+1 | |
| fig.add_trace(go.Scatter(x=strikes, y=v, line=dict(color=colors[i], width=2), name=k), row=row, col=col) | |
| fig.add_vline(x=S, line_dash='dash', line_color='gray', row=row, col=col) | |
| expiry_payoff = [max(s-K,0) if option_type.lower()=='call' else max(K-s,0) for s in strikes] | |
| pl_expiry = [p - bs['price'] for p in expiry_payoff] | |
| fig.add_trace(go.Scatter(x=strikes, y=pl_expiry, line=dict(color='#673AB7', width=2), name='P/L Expiry'), row=2, col=3) | |
| fig.add_hline(y=0, line_dash='dot', line_color='gray', row=2, col=3) | |
| fig.update_layout( | |
| title=f'{ticker} {option_type.title()} Greeks (S=${S:.2f}, K=${K:.2f}, T={days}d, σ={sigma*100:.1f}%)', | |
| template='plotly_dark', height=650, | |
| paper_bgcolor='#0d1117', plot_bgcolor='#161b22', font=dict(color='#e6edf3')) | |
| md = f"""## {ticker} {option_type.title()} Analysis | |
| | Parameter | Value | | |
| |-----------|-------| | |
| | Spot (S) | ${S:.2f} | | |
| | Strike (K) | ${K:.2f} ({strike_pct:.0f}% of spot) | | |
| | Time to Expiry | {days} days | | |
| | Risk-Free Rate | {r*100:.2f}% | | |
| | Volatility | {sigma*100:.1f}% | | |
| ### Greeks | |
| | Greek | Value | | |
| |-------|-------| | |
| | **Price** | ${bs['price']:.3f} | | |
| | **Delta** | {bs['delta']:.4f} | | |
| | **Gamma** | {bs['gamma']:.6f} | | |
| | **Theta** | ${bs['theta']:.4f}/day | | |
| | **Vega** | ${bs['vega']:.4f} | | |
| | **Rho** | ${bs['rho']:.4f} | | |
| | **d1** | {bs['d1']:.4f} | | |
| | **d2** | {bs['d2']:.4f} | | |
| ### P/L Scenarios (per 100 contracts) | |
| {pl_df.to_markdown(index=False)} | |
| """ | |
| return fig, pl_df, md | |
| # MACRO | |
| def get_macro_data(): | |
| macros = {} | |
| 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')]: | |
| try: | |
| df = yf.Ticker(t).history(period='1mo') | |
| if not df.empty: | |
| macros[name] = {'price': df['Close'].iloc[-1], 'change_1m': (df['Close'].iloc[-1]/df['Close'].iloc[0]-1)*100} | |
| except: | |
| pass | |
| return macros | |
| def ai_macro(): | |
| macros = get_macro_data() | |
| macro_text = "Global Macro Snapshot:\n" | |
| for name, data in macros.items(): | |
| macro_text += f"- {name}: ${data['price']:.2f} (1M change: {data['change_1m']:+.1f}%)\n" | |
| client = K2ThinkClient() | |
| return client.macro_analysis(macro_text) | |
| # UI FUNCTIONS | |
| def analyze_stock(ticker, market_preset, period, interval): | |
| ticker = ticker.strip().upper() | |
| if not ticker: | |
| return [None]*6 + ["Enter a ticker."] | |
| suffix = MARKET_PRESETS.get(market_preset, {}).get('suffix', '') | |
| if suffix and not any(ticker.endswith(s) for s in suffix.split('|')): | |
| ticker = ticker + suffix | |
| df, info, err = fetch_data(ticker, period) | |
| if df is None: | |
| return [None]*6 + [f"Error: {err}"] | |
| df = calc_indicators(df) | |
| sg = calc_signals(df) | |
| rk = calc_risk(df) | |
| if not rk: | |
| return [None]*6 + ["Need more data."] | |
| l = df.iloc[-1] | |
| p = df.iloc[-2] if len(df)>1 else l | |
| ch = ((l['Close']/p['Close']-1)*100) if p['Close']>0 else 0 | |
| c1 = make_candlestick(df, ticker, market_preset) | |
| c2 = make_macd(df, ticker) | |
| c3 = make_stoch(df, ticker) | |
| c4 = make_vol(df, ticker) | |
| c5 = make_adx(df, ticker) | |
| c6 = make_dist(df['Ret'].dropna(), ticker) | |
| info_lines = [] | |
| if info: | |
| info_lines.append(f"| Name | {info.get('longName', ticker)} |") | |
| info_lines.append(f"| Sector | {info.get('sector', 'N/A')} |") | |
| info_lines.append(f"| Industry | {info.get('industry', 'N/A')} |") | |
| if info.get('marketCap'): | |
| info_lines.append(f"| Market Cap | {info.get('marketCap'):,} |") | |
| if info.get('fiftyTwoWeekHigh'): | |
| info_lines.append(f"| 52W High | ${info.get('fiftyTwoWeekHigh'):.2f} |") | |
| if info.get('fiftyTwoWeekLow'): | |
| info_lines.append(f"| 52W Low | ${info.get('fiftyTwoWeekLow'):.2f} |") | |
| if info.get('trailingPE'): | |
| info_lines.append(f"| P/E | {info.get('trailingPE'):.2f} |") | |
| mkt_info = "\n".join(info_lines) | |
| md = f"""# {ticker} - {sg['dir']} {sg['strength']} (Score: {sg['score']}/100) | |
| **Price:** ${l['Close']:.2f} | **Change:** {ch:+.2f}% | **Period:** {period} | |
| {mkt_info} | |
| ## Signal Dashboard | |
| | Indicator | Value | Signal | | |
| |-----------|-------|--------| | |
| | RSI (14) | {l['RSI']:.1f} | {'🟢 Deeply Oversold' if l['RSI']<30 else '🔴 Deeply Overbought' if l['RSI']>70 else '⚪ Neutral'} | | |
| | MACD | {l['MACD']:.3f} | {'🟢 Bullish' if l['MACD']>l['MACDS'] else '🔴 Bearish'} | | |
| | Bollinger | {l['BBP']:.1%} | {'🔴 Upper' if l['BBP']>0.8 else '🟢 Lower' if l['BBP']<0.2 else '⚪ Mid'} | | |
| | VWAP | {'🟢 Above' if l['Close']>l['VWAP'] else '🔴 Below'} | {'🟢 Bullish' if l['Close']>l['VWAP'] else '🔴 Bearish'} | | |
| | Stoch %K | {l['Stoch_K']:.1f} | {'🟢 Oversold' if l['Stoch_K']<20 else '🔴 Overbought' if l['Stoch_K']>80 else '⚪ Neutral'} | | |
| | ADX | {l['ADX']:.1f} | {sg['adx_trend']} | | |
| | Volume | {l['VR']:.1f}x avg | {'🔥 Heavy' if l['VR']>2 else '⬆️ Above Avg' if l['VR']>1.5 else '⚪ Normal'} | | |
| | Trend | {sg['trend'].upper()} | — | | |
| | Momentum | {sg['mom']} | — | | |
| | Volatility | {sg['vol']} | — | | |
| | Ichimoku | {sg['ichimoku']} | — | | |
| ## Risk Metrics | |
| | Metric | Value | | |
| |--------|-------| | |
| | Ann. Return | {rk['ar']*100:.1f}% | | |
| | Ann. Volatility | {rk['av']*100:.1f}% | | |
| | Vol Regime | {rk['vol_regime'].upper()} | | |
| | Sharpe | {rk['sh']:.2f} | | |
| | Sortino | {rk['so']:.2f} | | |
| | Max Drawdown | {rk['md']*100:.1f}% | | |
| | VaR (95%) | {rk['v95']*100:.2f}% | | |
| | VaR (99%) | {rk['v99']*100:.2f}% | | |
| | CVaR (95%) | {rk['cv95']*100:.2f}% | | |
| | CVaR (99%) | {rk['cv99']*100:.2f}% | | |
| | Calmar | {rk['ca']:.2f} | | |
| | Win Rate | {rk['wr']*100:.1f}% | | |
| | Avg Win | {rk['avg_win']*100:.2f}% | | |
| | Avg Loss | {rk['avg_loss']*100:.2f}% | | |
| | Profit Factor | {rk['pf']:.2f} | | |
| | Skewness | {rk['sk']:.2f} | | |
| | Kurtosis | {rk['ku']:.2f} | | |
| | 63D Rolling Sharpe | {rk['roll_sharpe']:.2f} | | |
| """ | |
| return [c1, c2, c3, c4, c5, c6, md] | |
| def ai_analyze_stock(ticker, market_preset, period, interval): | |
| ticker = ticker.strip().upper() | |
| if not ticker: | |
| return "Enter a ticker." | |
| suffix = MARKET_PRESETS.get(market_preset, {}).get('suffix', '') | |
| if suffix and not any(ticker.endswith(s) for s in suffix.split('|')): | |
| ticker = ticker + suffix | |
| df, info, err = fetch_data(ticker, period) | |
| if df is None: | |
| return f"Error: {err}" | |
| df = calc_indicators(df) | |
| sg = calc_signals(df) | |
| rk = calc_risk(df) | |
| l = df.iloc[-1] | |
| p = df.iloc[-2] if len(df)>1 else l | |
| ch = ((l['Close']/p['Close']-1)*100) if p['Close']>0 else 0 | |
| data_sum = f"""Ticker: {ticker} | |
| Price: ${l['Close']:.2f} (Change: {ch:+.2f}%) | |
| Period: {period} | Data Points: {len(df)} | |
| SMA20: ${l['SMA20']:.2f} | SMA50: ${l['SMA50']:.2f} | SMA200: ${l['SMA200']:.2f} | |
| 52W Range: ${df['Low'].min():.2f} - ${df['High'].max():.2f} | |
| ATR: ${l['ATR']:.2f} ({l['ATR_pct']:.1f}% of price) | |
| Volume: {l['Volume']:,.0f} ({l['VR']:.1f}x avg) | |
| """ | |
| tech_sum = f"""RSI: {l['RSI']:.1f} | MACD: {l['MACD']:.3f} vs Signal: {l['MACDS']:.3f} | MACD Hist: {l['MACDH']:.3f} | |
| BB Position: {l['BBP']:.1%} | BB Width: {l['BBW']:.2f} | Stoch %K: {l['Stoch_K']:.1f} | |
| VWAP: ${l['VWAP']:.2f} | ADX: {l['ADX']:.1f} | MFI: {l['MFI']:.1f} | |
| Ichimoku: {sg['ichimoku']} | Cloud: Senkou A={l['ICH_senkou_A']:.2f}, B={l['ICH_senkou_B']:.2f} | |
| Score: {sg['score']}/100 | Direction: {sg['dir']} | Strength: {sg['strength']} | |
| Risk: Sharpe={rk.get('sh',0):.2f}, Vol={rk.get('av',0)*100:.1f}%, MaxDD={rk.get('md',0)*100:.1f}%, VaR95={rk.get('v95',0)*100:.2f}%""" | |
| client = K2ThinkClient() | |
| return client.analyze_market(ticker, market_preset, data_sum, tech_sum, period) | |
| def ai_portfolio(tickers, period): | |
| fig, corr_fig, wdf, md = optimize_portfolio(tickers, period) | |
| if fig is None: | |
| return f"Error: {md}" | |
| pd_str = f"Tickers: {', '.join(wdf['Ticker'].tolist())}\nWeights: {', '.join([f'{t}: {w:.1f}%' for t,w in zip(wdf['Ticker'], wdf['Optimal (%)'])])}" | |
| corr_str = f"Correlations:\n{wdf['Ticker'].tolist()}" | |
| client = K2ThinkClient() | |
| return client.portfolio_advice(pd_str, corr_str, md, "Current macro: mixed signals, rates elevated, geopolitical uncertainty") | |
| def ai_chat(question, temp): | |
| if not question.strip(): | |
| return "Enter a question." | |
| client = K2ThinkClient() | |
| return client.chat([{"role":"user","content":question}], temperature=temp, max_tokens=4096) | |
| # GRADIO APP | |
| def build_app(): | |
| with gr.Blocks( | |
| title="AlphaForge x K2 Think V2 - Institutional Quant Platform", | |
| theme=gr.themes.Soft( | |
| primary_hue="blue", secondary_hue="indigo", neutral_hue="slate", | |
| font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"] | |
| ), | |
| css=""" | |
| body { background: #0d1117 !important; } | |
| .gradio-container { background: #0d1117 !important; color: #e6edf3 !important; } | |
| .tabitem { background: #161b22 !important; border: 1px solid #30363d !important; border-radius: 12px !important; } | |
| .tab-nav { background: #0d1117 !important; border-bottom: 1px solid #30363d !important; } | |
| .tab-nav button { color: #8b949e !important; background: transparent !important; border: none !important; } | |
| .tab-nav button.selected { color: #58a6ff !important; border-bottom: 2px solid #58a6ff !important; } | |
| input, textarea, select { background: #21262d !important; color: #e6edf3 !important; border: 1px solid #30363d !important; } | |
| button.primary { background: linear-gradient(135deg, #1f6feb, #58a6ff) !important; color: white !important; border: none !important; border-radius: 8px !important; font-weight: 600 !important; } | |
| button.secondary { background: #21262d !important; color: #58a6ff !important; border: 1px solid #30363d !important; border-radius: 8px !important; } | |
| .title-bar { text-align: center; padding: 24px 0; } | |
| .title-bar h1 { font-size: 2.8em; font-weight: 800; margin: 0; background: linear-gradient(90deg, #58a6ff, #a371f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } | |
| .title-bar p { color: #8b949e; font-size: 1.1em; margin-top: 8px; } | |
| .badge-row { text-align: center; margin: 16px 0 24px; } | |
| .badge { display: inline-block; padding: 6px 14px; margin: 4px; border-radius: 20px; font-size: 0.85em; font-weight: 600; } | |
| .badge-api { background: linear-gradient(135deg, #1f6feb, #a371f7); color: white; } | |
| .badge-data { background: #238636; color: white; } | |
| .badge-alpha { background: #8957e5; color: white; } | |
| .k2-active { text-align: center; padding: 8px; margin: 8px 0; border-radius: 8px; font-size: 0.9em; background: rgba(35,134,54,0.2); color: #3fb950; border: 1px solid #238636; } | |
| .k2-inactive { text-align: center; padding: 8px; margin: 8px 0; border-radius: 8px; font-size: 0.9em; background: rgba(209,36,47,0.2); color: #f85149; border: 1px solid #da3633; } | |
| """ | |
| ) as demo: | |
| gr.HTML(""" | |
| <div class="title-bar"> | |
| <h1>🔥 AlphaForge x K2 Think V2</h1> | |
| <p>Institutional-Grade Quantitative Analysis Platform - Powered by MBZUAI's State-of-the-Art Reasoning Model</p> | |
| </div> | |
| <div class="badge-row"> | |
| <span class="badge badge-api">🤖 K2 Think V2</span> | |
| <span class="badge badge-data">📊 Multi-Market</span> | |
| <span class="badge badge-alpha">🎯 AI Alpha</span> | |
| <span class="badge badge-data">📐 Options</span> | |
| <span class="badge badge-alpha">🔗 Pairs</span> | |
| <span class="badge badge-api">🌍 Macro</span> | |
| </div> | |
| """) | |
| k2_cls = "k2-active" if K2_API_KEY else "k2-inactive" | |
| k2_txt = "✅ K2 Think V2 API Connected" if K2_API_KEY else "⚠️ K2 Think V2 Not Configured — Add K2_API_KEY in Space Settings > Repository Secrets" | |
| gr.HTML(f'<div class="{k2_cls}">{k2_txt}</div>') | |
| with gr.Tab("📈 Technical Analysis"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| mkt_select = gr.Dropdown(label="🌍 Market", choices=list(MARKET_PRESETS.keys()), value="🇺🇸 US Equities") | |
| ticker_in = gr.Textbox(label="Ticker", value="AAPL", placeholder="e.g., AAPL, BTC-USD, EURUSD=X") | |
| period_in = gr.Dropdown(label="Period", choices=["1mo","3mo","6mo","1y","2y","5y"], value="6mo") | |
| interval_in = gr.Dropdown(label="Interval", choices=["1d","1wk","1mo"], value="1d") | |
| analyze_btn = gr.Button("🔍 Analyze", variant="primary") | |
| ai_btn = gr.Button("🤖 AI Deep Analysis (K2 Think V2)", variant="secondary") | |
| with gr.Column(scale=2): | |
| summary_out = gr.Markdown() | |
| with gr.Row(): | |
| chart1 = gr.Plot(label="Price & Technicals") | |
| chart2 = gr.Plot(label="MACD") | |
| with gr.Row(): | |
| chart3 = gr.Plot(label="Stochastic") | |
| chart4 = gr.Plot(label="Volatility & Volume") | |
| with gr.Row(): | |
| chart5 = gr.Plot(label="ADX Trend Strength") | |
| chart6 = gr.Plot(label="Return Distribution") | |
| with gr.Row(): | |
| ai_out = gr.Textbox(label="🤖 K2 Think V2 Analysis", lines=30, max_lines=50) | |
| analyze_btn.click(fn=analyze_stock, inputs=[ticker_in, mkt_select, period_in, interval_in], | |
| outputs=[chart1, chart2, chart3, chart4, chart5, chart6, summary_out]) | |
| ai_btn.click(fn=ai_analyze_stock, inputs=[ticker_in, mkt_select, period_in, interval_in], | |
| outputs=[ai_out]) | |
| with gr.Tab("💼 Portfolio Optimizer"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| port_in = gr.Textbox(label="Tickers (comma-separated)", value="AAPL, MSFT, GOOGL, AMZN, NVDA") | |
| port_period = gr.Dropdown(label="Lookback", choices=["6mo","1y","2y","3y"], value="1y") | |
| opt_btn = gr.Button("🎯 Optimize", variant="primary") | |
| ai_port_btn = gr.Button("🤖 AI Portfolio Advice", variant="secondary") | |
| with gr.Column(scale=2): | |
| port_md = gr.Markdown() | |
| with gr.Row(): | |
| frontier_plot = gr.Plot(label="Efficient Frontier") | |
| corr_plot = gr.Plot(label="Correlation Matrix") | |
| with gr.Row(): | |
| weights_df = gr.DataFrame(label="Optimal Weights", interactive=False) | |
| with gr.Row(): | |
| ai_port_out = gr.Textbox(label="🤖 AI Portfolio Advice", lines=25, max_lines=40) | |
| opt_btn.click(fn=optimize_portfolio, inputs=[port_in, port_period], outputs=[frontier_plot, corr_plot, weights_df, port_md]) | |
| ai_port_btn.click(fn=ai_portfolio, inputs=[port_in, port_period], outputs=[ai_port_out]) | |
| with gr.Tab("🔗 Pairs Trading"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| pair_a = gr.Textbox(label="Ticker A (Long)", value="AAPL") | |
| pair_b = gr.Textbox(label="Ticker B (Short)", value="MSFT") | |
| pair_period = gr.Dropdown(label="Lookback", choices=["6mo","1y","2y"], value="1y") | |
| pair_btn = gr.Button("Analyze Pair", variant="primary") | |
| with gr.Column(scale=2): | |
| pair_md = gr.Markdown() | |
| with gr.Row(): | |
| pair_chart = gr.Plot(label="Spread Analysis") | |
| pair_scatter = gr.Plot(label="Price Relationship") | |
| pair_btn.click(fn=analyze_pair, inputs=[pair_a, pair_b, pair_period], outputs=[pair_chart, pair_scatter, pair_md]) | |
| with gr.Tab("📐 Options Pricing"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| opt_ticker = gr.Textbox(label="Underlying Ticker", value="AAPL") | |
| opt_type = gr.Dropdown(label="Option Type", choices=["Call","Put"], value="Call") | |
| opt_strike = gr.Slider(label="Strike (% of spot)", minimum=70, maximum=130, value=100, step=1) | |
| opt_days = gr.Slider(label="Days to Expiry", minimum=7, maximum=365, value=30, step=7) | |
| opt_rfr = gr.Slider(label="Risk-Free Rate (%)", minimum=0, maximum=10, value=4.5, step=0.25) | |
| opt_vol = gr.Number(label="Vol Override (%)", value=0, info="0 = use historical vol") | |
| opt_calc_btn = gr.Button("📐 Calculate Greeks", variant="primary") | |
| with gr.Column(scale=2): | |
| opt_md = gr.Markdown() | |
| with gr.Row(): | |
| greeks_plot = gr.Plot(label="Greeks Analysis") | |
| opt_pl = gr.DataFrame(label="P/L Scenarios", interactive=False) | |
| opt_calc_btn.click(fn=analyze_options, inputs=[opt_ticker, opt_strike, opt_days, opt_rfr, opt_vol, opt_type], | |
| outputs=[greeks_plot, opt_pl, opt_md]) | |
| with gr.Tab("🌍 Macro Analysis"): | |
| with gr.Row(): | |
| macro_btn = gr.Button("🌍 Analyze Global Macro (K2 Think V2)", variant="primary") | |
| with gr.Row(): | |
| macro_out = gr.Textbox(label="🤖 K2 Think V2 Macro Analysis", lines=40, max_lines=60) | |
| macro_btn.click(fn=ai_macro, outputs=[macro_out]) | |
| with gr.Tab("💬 K2 Think V2 Chat"): | |
| gr.Markdown("## Direct Chat with K2 Think V2") | |
| gr.Markdown("Ask any financial question - strategy, market analysis, quant interview prep, portfolio advice.") | |
| with gr.Row(): | |
| chat_in = gr.Textbox(label="Your Question", placeholder="e.g., 'Explain gamma scalping with a real trade example'", lines=4) | |
| chat_temp = gr.Slider(label="Temp", minimum=0, maximum=1, value=0.4, step=0.1) | |
| chat_btn = gr.Button("🚀 Ask K2 Think V2", variant="primary") | |
| chat_out = gr.Textbox(label="🤖 Response", lines=30, max_lines=50) | |
| chat_btn.click(fn=ai_chat, inputs=[chat_in, chat_temp], outputs=[chat_out]) | |
| with gr.Tab("ℹ️ About & Setup"): | |
| gr.Markdown(f""" | |
| ## AlphaForge x K2 Think V2 | |
| Built for the **Build with K2 Think V2 Challenge** by MBZUAI. | |
| ### Features | |
| | Feature | Description | | |
| |---------|-------------| | |
| | **📈 Technical Analysis** | 18+ indicators: RSI, MACD, Bollinger, VWAP, Stochastic, ADX, Ichimoku, ATR, MFI, OBV | | |
| | **🌍 Multi-Market** | US, EU, UK, DE, JP, CN, IN equities + Crypto + Forex + Commodities + Indices | | |
| | **💼 Portfolio** | Mean-variance optimization, efficient frontier, correlation matrix | | |
| | **🔗 Pairs Trading** | Cointegration analysis, hedge ratio, half-life, Z-score signals | | |
| | **📐 Options Pricing** | Black-Scholes + full Greeks (Delta, Gamma, Theta, Vega, Rho), P/L scenarios | | |
| | **🌍 Macro Analysis** | Global cross-asset regime analysis via K2 Think V2 | | |
| | **🤖 AI Analysis** | K2 Think V2 chain-of-thought: entry/stop/target, catalyst calendar, contrarian view | | |
| | **💬 AI Chat** | Ask any financial question with adjustable temperature | | |
| ### Setup | |
| > **K2 Think V2 API Key** | |
| > 1. Space Settings → Repository secrets | |
| > 2. New secret: `K2_API_KEY` = `IFM-4SpQ0qEg0Wlsw04O` | |
| > 3. Save → Factory Rebuild | |
| **Without API key:** All technical analysis, charts, portfolio optimization, pairs trading, and options pricing work perfectly! | |
| ### Links | |
| - [Full AlphaForge](https://huggingface.co/Premchan369/alphaforge-quant-system) (25 quant modules) | |
| - [Q-TensorFormer](https://huggingface.co/Premchan369/Q-TensorFormer) (Quantum AI) | |
| - [Challenge](https://build.k2think.ai/) | |
| - [MBZUAI](https://mbzuai.ac.ae/) | |
| *Built by Premchan | Build with K2 Think V2* | |
| """) | |
| return demo | |
| if __name__ == "__main__": | |
| demo = build_app() | |
| demo.queue().launch(server_name="0.0.0.0", server_port=7860) | |