Premchan369 commited on
Commit
3cfc8dd
Β·
verified Β·
1 Parent(s): 8b6fafc

Restore app.py + fix share=True for HF Spaces

Browse files
Files changed (1) hide show
  1. app.py +63 -6
app.py CHANGED
@@ -10,7 +10,7 @@ Features:
10
  - Alpha signal generation
11
  - AI-powered market analysis with chain-of-thought reasoning
12
 
13
- API Key: set via K2_API_KEY environment variable
14
  """
15
  import os
16
  import json
@@ -23,8 +23,6 @@ import numpy as np
23
  from datetime import datetime, timedelta
24
  import plotly.graph_objects as go
25
  from plotly.subplots import make_subplots
26
- from scipy import stats
27
- from scipy.optimize import minimize
28
  import warnings
29
  warnings.filterwarnings('ignore')
30
 
@@ -32,7 +30,7 @@ warnings.filterwarnings('ignore')
32
  # ──────────────────────────────────────────────────────────────
33
  # K2 Think V2 API Configuration
34
  # ──────────────────────────────────────────────────────────────
35
- K2_API_KEY = os.environ.get("K2_API_KEY")
36
  K2_BASE_URL = "https://api.k2think.ai/v1/chat/completions"
37
  K2_MODEL = "MBZUAI-IFM/K2-Think-v2"
38
 
@@ -178,22 +176,31 @@ def compute_technical_indicators(df: pd.DataFrame) -> pd.DataFrame:
178
  """Compute technical indicators"""
179
  df = df.copy()
180
 
 
181
  df['Returns'] = df['Close'].pct_change()
 
 
182
  df['SMA_20'] = df['Close'].rolling(20).mean()
183
  df['SMA_50'] = df['Close'].rolling(50).mean()
184
  df['SMA_200'] = df['Close'].rolling(200).mean()
 
 
185
  df['EMA_12'] = df['Close'].ewm(span=12, adjust=False).mean()
186
  df['EMA_26'] = df['Close'].ewm(span=26, adjust=False).mean()
 
 
187
  df['MACD'] = df['EMA_12'] - df['EMA_26']
188
  df['MACD_Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
189
  df['MACD_Histogram'] = df['MACD'] - df['MACD_Signal']
190
 
 
191
  delta = df['Close'].diff()
192
  gain = (delta.where(delta > 0, 0)).rolling(14).mean()
193
  loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
194
  rs = gain / (loss + 1e-10)
195
  df['RSI'] = 100 - (100 / (1 + rs))
196
 
 
197
  df['BB_Middle'] = df['Close'].rolling(20).mean()
198
  bb_std = df['Close'].rolling(20).std()
199
  df['BB_Upper'] = df['BB_Middle'] + 2 * bb_std
@@ -201,20 +208,24 @@ def compute_technical_indicators(df: pd.DataFrame) -> pd.DataFrame:
201
  df['BB_Width'] = (df['BB_Upper'] - df['BB_Lower']) / df['BB_Middle']
202
  df['BB_Position'] = (df['Close'] - df['BB_Lower']) / (df['BB_Upper'] - df['BB_Lower'] + 1e-10)
203
 
 
204
  typical_price = (df['High'] + df['Low'] + df['Close']) / 3
205
  df['VWAP'] = (typical_price * df['Volume']).cumsum() / (df['Volume'].cumsum() + 1e-10)
206
 
 
207
  high_low = df['High'] - df['Low']
208
  high_close = np.abs(df['High'] - df['Close'].shift())
209
  low_close = np.abs(df['Low'] - df['Close'].shift())
210
  tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
211
  df['ATR'] = tr.rolling(14).mean()
212
 
 
213
  low_14 = df['Low'].rolling(14).min()
214
  high_14 = df['High'].rolling(14).max()
215
  df['Stoch_K'] = 100 * (df['Close'] - low_14) / (high_14 - low_14 + 1e-10)
216
  df['Stoch_D'] = df['Stoch_K'].rolling(3).mean()
217
 
 
218
  df['Volume_MA'] = df['Volume'].rolling(20).mean()
219
  df['Volume_Ratio'] = df['Volume'] / (df['Volume_MA'] + 1e-10)
220
 
@@ -235,11 +246,13 @@ def generate_signals(df: pd.DataFrame) -> dict:
235
  'confidence': 50
236
  }
237
 
 
238
  if latest['Close'] > latest['SMA_20'] > latest['SMA_50']:
239
  signals['trend'] = 'bullish'
240
  elif latest['Close'] < latest['SMA_20'] < latest['SMA_50']:
241
  signals['trend'] = 'bearish'
242
 
 
243
  if latest['RSI'] < 30:
244
  signals['momentum'] = 'oversold (bullish bounce potential)'
245
  elif latest['RSI'] > 70:
@@ -249,15 +262,18 @@ def generate_signals(df: pd.DataFrame) -> dict:
249
  elif latest['MACD'] < latest['MACD_Signal'] and prev['MACD'] >= prev['MACD_Signal']:
250
  signals['momentum'] = 'MACD bearish crossover'
251
 
 
252
  bb_width = latest['BB_Width']
253
  if bb_width > df['BB_Width'].quantile(0.9):
254
  signals['volatility'] = 'expanding (breakout likely)'
255
  elif bb_width < df['BB_Width'].quantile(0.1):
256
  signals['volatility'] = 'contracting (squeeze setup)'
257
 
 
258
  if latest['Volume_Ratio'] > 2.0:
259
  signals['volume'] = 'heavy (institutional interest)'
260
 
 
261
  score = 50
262
  if signals['trend'] == 'bullish': score += 15
263
  if signals['trend'] == 'bearish': score -= 15
@@ -270,6 +286,7 @@ def generate_signals(df: pd.DataFrame) -> dict:
270
 
271
  signals['composite_score'] = max(0, min(100, score))
272
 
 
273
  bullish_count = sum([
274
  signals['trend'] == 'bullish',
275
  'oversold' in signals['momentum'] or 'bullish crossover' in signals['momentum'],
@@ -294,24 +311,31 @@ def compute_risk_metrics(df: pd.DataFrame) -> dict:
294
  if len(returns) < 30:
295
  return {}
296
 
 
297
  annual_return = returns.mean() * 252
298
  annual_vol = returns.std() * np.sqrt(252)
299
  sharpe = annual_return / (annual_vol + 1e-10)
300
 
 
301
  downside = returns[returns < 0]
302
  downside_dev = downside.std() * np.sqrt(252) if len(downside) > 0 else 1e-10
303
  sortino = annual_return / (downside_dev + 1e-10)
304
 
 
305
  cumulative = (1 + returns).cumprod()
306
  running_max = cumulative.expanding().max()
307
  drawdown = (cumulative - running_max) / running_max
308
  max_dd = drawdown.min()
309
 
 
310
  var_95 = np.percentile(returns, 5)
311
  var_99 = np.percentile(returns, 1)
312
  cvar_95 = returns[returns <= var_95].mean() if len(returns[returns <= var_95]) > 0 else var_95
313
 
 
314
  calmar = annual_return / (abs(max_dd) + 1e-10)
 
 
315
  skew = returns.skew()
316
  kurt = returns.kurtosis()
317
 
@@ -347,6 +371,7 @@ def create_candlestick_chart(df: pd.DataFrame, ticker: str):
347
  subplot_titles=(f'{ticker} Price', 'Volume', 'RSI')
348
  )
349
 
 
350
  fig.add_trace(go.Candlestick(
351
  x=df.index,
352
  open=df['Open'],
@@ -356,11 +381,13 @@ def create_candlestick_chart(df: pd.DataFrame, ticker: str):
356
  name='Price'
357
  ), row=1, col=1)
358
 
 
359
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA_20'],
360
  line=dict(color='orange', width=1), name='SMA 20'), row=1, col=1)
361
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA_50'],
362
  line=dict(color='blue', width=1), name='SMA 50'), row=1, col=1)
363
 
 
364
  fig.add_trace(go.Scatter(x=df.index, y=df['BB_Upper'],
365
  line=dict(color='gray', width=1, dash='dash'),
366
  name='BB Upper', opacity=0.5), row=1, col=1)
@@ -368,11 +395,13 @@ def create_candlestick_chart(df: pd.DataFrame, ticker: str):
368
  line=dict(color='gray', width=1, dash='dash'),
369
  name='BB Lower', opacity=0.5), row=1, col=1)
370
 
 
371
  colors = ['green' if df['Close'].iloc[i] >= df['Open'].iloc[i] else 'red'
372
  for i in range(len(df))]
373
  fig.add_trace(go.Bar(x=df.index, y=df['Volume'],
374
  marker_color=colors, name='Volume', opacity=0.7), row=2, col=1)
375
 
 
376
  fig.add_trace(go.Scatter(x=df.index, y=df['RSI'],
377
  line=dict(color='purple', width=1.5), name='RSI'), row=3, col=1)
378
  fig.add_hline(y=70, line_dash="dash", line_color="red", row=3, col=1)
@@ -430,6 +459,7 @@ def create_return_distribution(returns: pd.Series, ticker: str):
430
  opacity=0.7
431
  ))
432
 
 
433
  x_range = np.linspace(returns.min(), returns.max(), 100)
434
  mu, sigma = returns.mean(), returns.std()
435
  normal_pdf = len(returns) * (x_range[1] - x_range[0]) * stats.norm.pdf(x_range, mu, sigma)
@@ -455,6 +485,7 @@ def create_return_distribution(returns: pd.Series, ticker: str):
455
  def optimize_portfolio(tickers: list, period: str = "1y"):
456
  """Mean-variance optimization for a portfolio"""
457
  try:
 
458
  data = {}
459
  for t in tickers:
460
  t = t.strip().upper()
@@ -467,6 +498,7 @@ def optimize_portfolio(tickers: list, period: str = "1y"):
467
  if len(data) < 2:
468
  return None, "Need at least 2 valid tickers for portfolio optimization."
469
 
 
470
  prices = pd.DataFrame(data)
471
  prices = prices.dropna()
472
  returns = prices.pct_change().dropna()
@@ -474,8 +506,11 @@ def optimize_portfolio(tickers: list, period: str = "1y"):
474
  if len(returns) < 30:
475
  return None, "Insufficient data after alignment."
476
 
 
477
  mu = returns.mean() * 252
478
  sigma = returns.cov() * 252
 
 
479
  n = len(mu)
480
 
481
  def neg_sharpe(weights):
@@ -485,16 +520,19 @@ def optimize_portfolio(tickers: list, period: str = "1y"):
485
  return -(port_return / (port_vol + 1e-10))
486
 
487
  constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
488
- bounds = tuple((0, 0.5) for _ in range(n))
489
  x0 = np.ones(n) / n
490
 
491
  result = minimize(neg_sharpe, x0, method='SLSQP', bounds=bounds, constraints=constraints)
 
492
  optimal_weights = result.x
493
 
 
494
  port_return = np.dot(optimal_weights, mu)
495
  port_vol = np.sqrt(np.dot(optimal_weights.T, np.dot(sigma, optimal_weights)))
496
  port_sharpe = port_return / (port_vol + 1e-10)
497
 
 
498
  eq_weights = np.ones(n) / n
499
  eq_return = np.dot(eq_weights, mu)
500
  eq_vol = np.sqrt(np.dot(eq_weights.T, np.dot(sigma, eq_weights)))
@@ -524,6 +562,7 @@ def create_efficient_frontier(opt_result: dict):
524
  sigma = opt_result['covariance']
525
  n = len(mu)
526
 
 
527
  n_portfolios = 5000
528
  weights = np.random.dirichlet(np.ones(n), n_portfolios)
529
 
@@ -533,6 +572,7 @@ def create_efficient_frontier(opt_result: dict):
533
 
534
  fig = go.Figure()
535
 
 
536
  fig.add_trace(go.Scatter(
537
  x=port_vols, y=port_returns,
538
  mode='markers',
@@ -546,6 +586,7 @@ def create_efficient_frontier(opt_result: dict):
546
  name='Random Portfolios'
547
  ))
548
 
 
549
  fig.add_trace(go.Scatter(
550
  x=[opt_result['optimal_volatility']],
551
  y=[opt_result['optimal_return']],
@@ -556,6 +597,7 @@ def create_efficient_frontier(opt_result: dict):
556
  name='Optimal Portfolio'
557
  ))
558
 
 
559
  fig.add_trace(go.Scatter(
560
  x=[opt_result['equal_volatility']],
561
  y=[opt_result['equal_return']],
@@ -587,17 +629,21 @@ def analyze_ticker(ticker: str, period: str = "6mo"):
587
  return None, None, None, None, "Please enter a ticker symbol."
588
 
589
  df, info = fetch_stock_data(ticker, period=period)
 
590
  if df is None:
591
  return None, None, None, None, info
592
 
 
593
  df = compute_technical_indicators(df)
594
  signals = generate_signals(df)
595
  risk = compute_risk_metrics(df)
596
 
 
597
  candlestick = create_candlestick_chart(df, ticker)
598
  macd_chart = create_macd_chart(df, ticker)
599
  returns_chart = create_return_distribution(df['Returns'].dropna(), ticker)
600
 
 
601
  latest = df.iloc[-1]
602
  prev = df.iloc[-2] if len(df) > 1 else latest
603
 
@@ -652,6 +698,7 @@ def ai_analyze(ticker: str, period: str = "6mo"):
652
  risk = compute_risk_metrics(df)
653
  latest = df.iloc[-1]
654
 
 
655
  data_summary = f"""
656
  Ticker: {ticker}
657
  Current Price: ${latest['Close']:.2f}
@@ -685,6 +732,7 @@ Max Drawdown: {risk.get('max_drawdown', 0)*100:.1f}%
685
  VaR 95%: {risk.get('var_95_daily', 0)*100:.2f}%
686
  """
687
 
 
688
  client = K2ThinkClient()
689
  analysis = client.analyze_market(ticker, data_summary, technical_summary)
690
 
@@ -698,19 +746,23 @@ def analyze_portfolio(tickers_str: str, period: str = "1y"):
698
  if len(tickers) < 2:
699
  return None, None, "Please enter at least 2 tickers separated by commas."
700
 
 
701
  result, error = optimize_portfolio(tickers, period)
702
 
703
  if error:
704
  return None, None, error
705
 
 
706
  frontier = create_efficient_frontier(result)
707
 
 
708
  weights_df = pd.DataFrame({
709
  'Ticker': result['tickers'],
710
  'Optimal Weight (%)': result['optimal_weights'] * 100,
711
  'Equal Weight (%)': result['equal_weights'] * 100
712
  })
713
 
 
714
  summary = f"""## Portfolio Optimization Results
715
 
716
  **Tickers:** {', '.join(result['tickers'])}
@@ -754,6 +806,7 @@ def ai_portfolio_advice(tickers_str: str, period: str = "1y"):
754
  if error:
755
  return error
756
 
 
757
  portfolio_data = f"""
758
  Holdings:
759
  {chr(10).join([f"- {t}: {w*100:.1f}%" for t, w in zip(result['tickers'], result['optimal_weights'])])}
@@ -766,9 +819,10 @@ Individual Asset Returns (annual):
766
  {chr(10).join([f"- {t}: {r*100:.1f}%" for t, r in zip(result['tickers'], result['annual_returns'])])}
767
  """
768
 
 
769
  corr = result['covariance']
770
  for i in range(len(corr)):
771
- corr.iloc[i, i] = np.nan
772
 
773
  risk_metrics = f"""
774
  Correlation Matrix (off-diagonal):
@@ -802,6 +856,7 @@ def build_app():
802
  """
803
  ) as demo:
804
 
 
805
  gr.HTML("""
806
  <div class="main-title">πŸ”₯ AlphaForge x K2 Think V2</div>
807
  <div class="subtitle">Elite Quantitative Trading Platform powered by MBZUAI's State-of-the-Art Reasoning Model</div>
@@ -847,6 +902,7 @@ def build_app():
847
 
848
  error_output = gr.Textbox(label="Status", visible=False)
849
 
 
850
  analyze_btn.click(
851
  fn=analyze_ticker,
852
  inputs=[ticker_input, period_input],
@@ -985,6 +1041,7 @@ def build_app():
985
  if __name__ == "__main__":
986
  demo = build_app()
987
 
 
988
  demo.queue().launch(
989
  server_name="0.0.0.0",
990
  server_port=7860,
 
10
  - Alpha signal generation
11
  - AI-powered market analysis with chain-of-thought reasoning
12
 
13
+ API Key: IFM-4SpQ0qEg0Wlsw04O
14
  """
15
  import os
16
  import json
 
23
  from datetime import datetime, timedelta
24
  import plotly.graph_objects as go
25
  from plotly.subplots import make_subplots
 
 
26
  import warnings
27
  warnings.filterwarnings('ignore')
28
 
 
30
  # ──────────────────────────────────────────────────────────────
31
  # K2 Think V2 API Configuration
32
  # ──────────────────────────────────────────────────────────────
33
+ K2_API_KEY = os.environ.get("K2_API_KEY", "IFM-4SpQ0qEg0Wlsw04O")
34
  K2_BASE_URL = "https://api.k2think.ai/v1/chat/completions"
35
  K2_MODEL = "MBZUAI-IFM/K2-Think-v2"
36
 
 
176
  """Compute technical indicators"""
177
  df = df.copy()
178
 
179
+ # Returns
180
  df['Returns'] = df['Close'].pct_change()
181
+
182
+ # Simple Moving Averages
183
  df['SMA_20'] = df['Close'].rolling(20).mean()
184
  df['SMA_50'] = df['Close'].rolling(50).mean()
185
  df['SMA_200'] = df['Close'].rolling(200).mean()
186
+
187
+ # EMA
188
  df['EMA_12'] = df['Close'].ewm(span=12, adjust=False).mean()
189
  df['EMA_26'] = df['Close'].ewm(span=26, adjust=False).mean()
190
+
191
+ # MACD
192
  df['MACD'] = df['EMA_12'] - df['EMA_26']
193
  df['MACD_Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
194
  df['MACD_Histogram'] = df['MACD'] - df['MACD_Signal']
195
 
196
+ # RSI
197
  delta = df['Close'].diff()
198
  gain = (delta.where(delta > 0, 0)).rolling(14).mean()
199
  loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
200
  rs = gain / (loss + 1e-10)
201
  df['RSI'] = 100 - (100 / (1 + rs))
202
 
203
+ # Bollinger Bands
204
  df['BB_Middle'] = df['Close'].rolling(20).mean()
205
  bb_std = df['Close'].rolling(20).std()
206
  df['BB_Upper'] = df['BB_Middle'] + 2 * bb_std
 
208
  df['BB_Width'] = (df['BB_Upper'] - df['BB_Lower']) / df['BB_Middle']
209
  df['BB_Position'] = (df['Close'] - df['BB_Lower']) / (df['BB_Upper'] - df['BB_Lower'] + 1e-10)
210
 
211
+ # VWAP
212
  typical_price = (df['High'] + df['Low'] + df['Close']) / 3
213
  df['VWAP'] = (typical_price * df['Volume']).cumsum() / (df['Volume'].cumsum() + 1e-10)
214
 
215
+ # ATR (Average True Range)
216
  high_low = df['High'] - df['Low']
217
  high_close = np.abs(df['High'] - df['Close'].shift())
218
  low_close = np.abs(df['Low'] - df['Close'].shift())
219
  tr = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
220
  df['ATR'] = tr.rolling(14).mean()
221
 
222
+ # Stochastic Oscillator
223
  low_14 = df['Low'].rolling(14).min()
224
  high_14 = df['High'].rolling(14).max()
225
  df['Stoch_K'] = 100 * (df['Close'] - low_14) / (high_14 - low_14 + 1e-10)
226
  df['Stoch_D'] = df['Stoch_K'].rolling(3).mean()
227
 
228
+ # Volume indicators
229
  df['Volume_MA'] = df['Volume'].rolling(20).mean()
230
  df['Volume_Ratio'] = df['Volume'] / (df['Volume_MA'] + 1e-10)
231
 
 
246
  'confidence': 50
247
  }
248
 
249
+ # Trend signal
250
  if latest['Close'] > latest['SMA_20'] > latest['SMA_50']:
251
  signals['trend'] = 'bullish'
252
  elif latest['Close'] < latest['SMA_20'] < latest['SMA_50']:
253
  signals['trend'] = 'bearish'
254
 
255
+ # Momentum signal
256
  if latest['RSI'] < 30:
257
  signals['momentum'] = 'oversold (bullish bounce potential)'
258
  elif latest['RSI'] > 70:
 
262
  elif latest['MACD'] < latest['MACD_Signal'] and prev['MACD'] >= prev['MACD_Signal']:
263
  signals['momentum'] = 'MACD bearish crossover'
264
 
265
+ # Volatility signal
266
  bb_width = latest['BB_Width']
267
  if bb_width > df['BB_Width'].quantile(0.9):
268
  signals['volatility'] = 'expanding (breakout likely)'
269
  elif bb_width < df['BB_Width'].quantile(0.1):
270
  signals['volatility'] = 'contracting (squeeze setup)'
271
 
272
+ # Volume signal
273
  if latest['Volume_Ratio'] > 2.0:
274
  signals['volume'] = 'heavy (institutional interest)'
275
 
276
+ # Composite score (0-100, >60 bullish, <40 bearish)
277
  score = 50
278
  if signals['trend'] == 'bullish': score += 15
279
  if signals['trend'] == 'bearish': score -= 15
 
286
 
287
  signals['composite_score'] = max(0, min(100, score))
288
 
289
+ # Confidence based on indicator agreement
290
  bullish_count = sum([
291
  signals['trend'] == 'bullish',
292
  'oversold' in signals['momentum'] or 'bullish crossover' in signals['momentum'],
 
311
  if len(returns) < 30:
312
  return {}
313
 
314
+ # Basic stats
315
  annual_return = returns.mean() * 252
316
  annual_vol = returns.std() * np.sqrt(252)
317
  sharpe = annual_return / (annual_vol + 1e-10)
318
 
319
+ # Sortino (downside deviation)
320
  downside = returns[returns < 0]
321
  downside_dev = downside.std() * np.sqrt(252) if len(downside) > 0 else 1e-10
322
  sortino = annual_return / (downside_dev + 1e-10)
323
 
324
+ # Max drawdown
325
  cumulative = (1 + returns).cumprod()
326
  running_max = cumulative.expanding().max()
327
  drawdown = (cumulative - running_max) / running_max
328
  max_dd = drawdown.min()
329
 
330
+ # VaR / CVaR
331
  var_95 = np.percentile(returns, 5)
332
  var_99 = np.percentile(returns, 1)
333
  cvar_95 = returns[returns <= var_95].mean() if len(returns[returns <= var_95]) > 0 else var_95
334
 
335
+ # Calmar
336
  calmar = annual_return / (abs(max_dd) + 1e-10)
337
+
338
+ # Skewness and kurtosis
339
  skew = returns.skew()
340
  kurt = returns.kurtosis()
341
 
 
371
  subplot_titles=(f'{ticker} Price', 'Volume', 'RSI')
372
  )
373
 
374
+ # Candlestick
375
  fig.add_trace(go.Candlestick(
376
  x=df.index,
377
  open=df['Open'],
 
381
  name='Price'
382
  ), row=1, col=1)
383
 
384
+ # SMAs
385
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA_20'],
386
  line=dict(color='orange', width=1), name='SMA 20'), row=1, col=1)
387
  fig.add_trace(go.Scatter(x=df.index, y=df['SMA_50'],
388
  line=dict(color='blue', width=1), name='SMA 50'), row=1, col=1)
389
 
390
+ # Bollinger Bands
391
  fig.add_trace(go.Scatter(x=df.index, y=df['BB_Upper'],
392
  line=dict(color='gray', width=1, dash='dash'),
393
  name='BB Upper', opacity=0.5), row=1, col=1)
 
395
  line=dict(color='gray', width=1, dash='dash'),
396
  name='BB Lower', opacity=0.5), row=1, col=1)
397
 
398
+ # Volume
399
  colors = ['green' if df['Close'].iloc[i] >= df['Open'].iloc[i] else 'red'
400
  for i in range(len(df))]
401
  fig.add_trace(go.Bar(x=df.index, y=df['Volume'],
402
  marker_color=colors, name='Volume', opacity=0.7), row=2, col=1)
403
 
404
+ # RSI
405
  fig.add_trace(go.Scatter(x=df.index, y=df['RSI'],
406
  line=dict(color='purple', width=1.5), name='RSI'), row=3, col=1)
407
  fig.add_hline(y=70, line_dash="dash", line_color="red", row=3, col=1)
 
459
  opacity=0.7
460
  ))
461
 
462
+ # Add normal overlay
463
  x_range = np.linspace(returns.min(), returns.max(), 100)
464
  mu, sigma = returns.mean(), returns.std()
465
  normal_pdf = len(returns) * (x_range[1] - x_range[0]) * stats.norm.pdf(x_range, mu, sigma)
 
485
  def optimize_portfolio(tickers: list, period: str = "1y"):
486
  """Mean-variance optimization for a portfolio"""
487
  try:
488
+ # Fetch data
489
  data = {}
490
  for t in tickers:
491
  t = t.strip().upper()
 
498
  if len(data) < 2:
499
  return None, "Need at least 2 valid tickers for portfolio optimization."
500
 
501
+ # Align and compute returns
502
  prices = pd.DataFrame(data)
503
  prices = prices.dropna()
504
  returns = prices.pct_change().dropna()
 
506
  if len(returns) < 30:
507
  return None, "Insufficient data after alignment."
508
 
509
+ # Expected returns and covariance
510
  mu = returns.mean() * 252
511
  sigma = returns.cov() * 252
512
+
513
+ # Optimize: max Sharpe
514
  n = len(mu)
515
 
516
  def neg_sharpe(weights):
 
520
  return -(port_return / (port_vol + 1e-10))
521
 
522
  constraints = {'type': 'eq', 'fun': lambda w: np.sum(w) - 1}
523
+ bounds = tuple((0, 0.5) for _ in range(n)) # Max 50% per asset
524
  x0 = np.ones(n) / n
525
 
526
  result = minimize(neg_sharpe, x0, method='SLSQP', bounds=bounds, constraints=constraints)
527
+
528
  optimal_weights = result.x
529
 
530
+ # Portfolio metrics
531
  port_return = np.dot(optimal_weights, mu)
532
  port_vol = np.sqrt(np.dot(optimal_weights.T, np.dot(sigma, optimal_weights)))
533
  port_sharpe = port_return / (port_vol + 1e-10)
534
 
535
+ # Equal weight for comparison
536
  eq_weights = np.ones(n) / n
537
  eq_return = np.dot(eq_weights, mu)
538
  eq_vol = np.sqrt(np.dot(eq_weights.T, np.dot(sigma, eq_weights)))
 
562
  sigma = opt_result['covariance']
563
  n = len(mu)
564
 
565
+ # Generate random portfolios
566
  n_portfolios = 5000
567
  weights = np.random.dirichlet(np.ones(n), n_portfolios)
568
 
 
572
 
573
  fig = go.Figure()
574
 
575
+ # Scatter of random portfolios
576
  fig.add_trace(go.Scatter(
577
  x=port_vols, y=port_returns,
578
  mode='markers',
 
586
  name='Random Portfolios'
587
  ))
588
 
589
+ # Optimal portfolio
590
  fig.add_trace(go.Scatter(
591
  x=[opt_result['optimal_volatility']],
592
  y=[opt_result['optimal_return']],
 
597
  name='Optimal Portfolio'
598
  ))
599
 
600
+ # Equal weight
601
  fig.add_trace(go.Scatter(
602
  x=[opt_result['equal_volatility']],
603
  y=[opt_result['equal_return']],
 
629
  return None, None, None, None, "Please enter a ticker symbol."
630
 
631
  df, info = fetch_stock_data(ticker, period=period)
632
+
633
  if df is None:
634
  return None, None, None, None, info
635
 
636
+ # Compute indicators
637
  df = compute_technical_indicators(df)
638
  signals = generate_signals(df)
639
  risk = compute_risk_metrics(df)
640
 
641
+ # Create charts
642
  candlestick = create_candlestick_chart(df, ticker)
643
  macd_chart = create_macd_chart(df, ticker)
644
  returns_chart = create_return_distribution(df['Returns'].dropna(), ticker)
645
 
646
+ # Summary text
647
  latest = df.iloc[-1]
648
  prev = df.iloc[-2] if len(df) > 1 else latest
649
 
 
698
  risk = compute_risk_metrics(df)
699
  latest = df.iloc[-1]
700
 
701
+ # Build data summary
702
  data_summary = f"""
703
  Ticker: {ticker}
704
  Current Price: ${latest['Close']:.2f}
 
732
  VaR 95%: {risk.get('var_95_daily', 0)*100:.2f}%
733
  """
734
 
735
+ # Call K2 Think V2
736
  client = K2ThinkClient()
737
  analysis = client.analyze_market(ticker, data_summary, technical_summary)
738
 
 
746
  if len(tickers) < 2:
747
  return None, None, "Please enter at least 2 tickers separated by commas."
748
 
749
+ # Optimize
750
  result, error = optimize_portfolio(tickers, period)
751
 
752
  if error:
753
  return None, None, error
754
 
755
+ # Create efficient frontier
756
  frontier = create_efficient_frontier(result)
757
 
758
+ # Weights comparison
759
  weights_df = pd.DataFrame({
760
  'Ticker': result['tickers'],
761
  'Optimal Weight (%)': result['optimal_weights'] * 100,
762
  'Equal Weight (%)': result['equal_weights'] * 100
763
  })
764
 
765
+ # Summary
766
  summary = f"""## Portfolio Optimization Results
767
 
768
  **Tickers:** {', '.join(result['tickers'])}
 
806
  if error:
807
  return error
808
 
809
+ # Build portfolio data
810
  portfolio_data = f"""
811
  Holdings:
812
  {chr(10).join([f"- {t}: {w*100:.1f}%" for t, w in zip(result['tickers'], result['optimal_weights'])])}
 
819
  {chr(10).join([f"- {t}: {r*100:.1f}%" for t, r in zip(result['tickers'], result['annual_returns'])])}
820
  """
821
 
822
+ # Correlation matrix
823
  corr = result['covariance']
824
  for i in range(len(corr)):
825
+ corr.iloc[i, i] = np.nan # Hide diagonal
826
 
827
  risk_metrics = f"""
828
  Correlation Matrix (off-diagonal):
 
856
  """
857
  ) as demo:
858
 
859
+ # Header
860
  gr.HTML("""
861
  <div class="main-title">πŸ”₯ AlphaForge x K2 Think V2</div>
862
  <div class="subtitle">Elite Quantitative Trading Platform powered by MBZUAI's State-of-the-Art Reasoning Model</div>
 
902
 
903
  error_output = gr.Textbox(label="Status", visible=False)
904
 
905
+ # Bind buttons
906
  analyze_btn.click(
907
  fn=analyze_ticker,
908
  inputs=[ticker_input, period_input],
 
1041
  if __name__ == "__main__":
1042
  demo = build_app()
1043
 
1044
+ # For HuggingFace Spaces
1045
  demo.queue().launch(
1046
  server_name="0.0.0.0",
1047
  server_port=7860,