mr601s commited on
Commit
a9f1ccb
Β·
verified Β·
1 Parent(s): 29e3231

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -422
app.py CHANGED
@@ -1,11 +1,3 @@
1
- # app.py β€” Bullish Minds AI: Stock Research (original preserved) + Trading Curriculum (isolated)
2
- # Run: pip install gradio requests numpy pandas feedparser
3
- # Launch: python app.py
4
- # Notes:
5
- # - Polygon free plan: previous close endpoint used.
6
- # - Finnhub metrics require token.
7
- # - SEC fetch requires descriptive User-Agent.
8
-
9
  import gradio as gr
10
  import requests
11
  import numpy as np
@@ -16,14 +8,11 @@ import random
16
  from datetime import datetime
17
  import os
18
 
19
- # =========================================================
20
- # =============== ORIGINAL RESEARCH FEATURES ==============
21
- # =========================================================
22
-
23
- # API keys (ENV or original fallbacks)
24
  POLYGON_API_KEY = os.getenv("POLYGON_API_KEY") or "fAhg47wPlf4FT6U2Hn23kQoQCQIyW0G_"
25
- FINNHUB_API_KEY = os.getenv("FINNHUB_API_KEY") or "d2urs69r01qq994h1f5gd2urs69r01qq994h1f60"
26
 
 
27
  def fetch_polygon_quote(ticker, polygon_api_key=POLYGON_API_KEY):
28
  url = f"https://api.polygon.io/v2/aggs/ticker/{ticker.upper()}/prev?adjusted=true&apiKey={polygon_api_key}"
29
  try:
@@ -31,47 +20,45 @@ def fetch_polygon_quote(ticker, polygon_api_key=POLYGON_API_KEY):
31
  response.raise_for_status()
32
  data = response.json()
33
  if data.get("results"):
34
- last = data["results"]
35
- price = last.get("c")
36
- ts = last.get("t")
37
- if price is None or ts is None:
38
- return f"❌ Quote format unexpected for {ticker.upper()}."
39
- close_dt = datetime.utcfromtimestamp(ts / 1000).strftime('%Y-%m-%d')
40
  return f"πŸ’° **Previous Close for {ticker.upper()} (as of {close_dt})**\n\nβ€’ **Close Price:** ${price:.2f}\n\n_(Free Polygon plan only provides prior close)_"
41
  else:
42
  return f"❌ Quote data unavailable for {ticker.upper()}."
43
  except Exception as e:
44
  return f"❌ Error: {str(e)}"
45
 
 
46
  def get_financial_summary_finnhub(ticker, finnhub_api_key=FINNHUB_API_KEY):
47
  url = f"https://finnhub.io/api/v1/stock/metric?symbol={ticker.upper()}&metric=all&token={finnhub_api_key}"
48
  try:
49
  response = requests.get(url, timeout=10)
50
  response.raise_for_status()
51
  data = response.json()
52
- metrics = (data or {}).get('metric', {}) or {}
53
  if not metrics:
54
  return f"πŸ“Š **Financial Summary for {ticker.upper()}**\n\n❌ No financial data found."
55
  result = f"πŸ“Š **Financial Summary for {ticker.upper()}**\n\n"
56
- if metrics.get('totalRevenueTTM') is not None:
57
  result += f"β€’ **Revenue (TTM):** ${int(metrics['totalRevenueTTM']):,}\n"
58
- if metrics.get('netIncomeTTM') is not None:
59
  result += f"β€’ **Net Income (TTM):** ${int(metrics['netIncomeTTM']):,}\n"
60
  pe = metrics.get('peNormalizedAnnual') or metrics.get('peExclExtraTTM')
61
  if pe is not None:
62
- result += f"β€’ **P/E Ratio:** {float(pe):.2f}\n"
63
  pb = metrics.get('pbAnnual')
64
  if pb is not None:
65
- result += f"β€’ **P/B Ratio:** {float(pb):.2f}\n"
66
  dy = metrics.get('dividendYieldIndicatedAnnual')
67
  if dy is not None:
68
- result += f"β€’ **Dividend Yield:** {float(dy):.2f}%\n"
69
  dte = metrics.get('totalDebt/totalEquityAnnual')
70
  if dte is not None:
71
- result += f"β€’ **Debt/Equity:** {float(dte):.2f}\n"
72
  pm = metrics.get('netProfitMarginTTM')
73
  if pm is not None:
74
- result += f"β€’ **Net Profit Margin:** {float(pm):.2f}%\n"
75
  mc = metrics.get('marketCapitalization')
76
  if mc is not None:
77
  result += f"β€’ **Market Cap:** ${int(mc):,}\n"
@@ -81,12 +68,12 @@ def get_financial_summary_finnhub(ticker, finnhub_api_key=FINNHUB_API_KEY):
81
  except Exception as e:
82
  return f"πŸ“Š **Financial Summary for {ticker.upper()}**\n\n❌ Error fetching financial summary: {e}"
83
 
 
84
  class SECUtils:
85
  def __init__(self):
86
  self.cik_lookup_url = "https://www.sec.gov/files/company_tickers.json"
87
  self.edgar_search_url = "https://data.sec.gov/submissions/CIK{cik}.json"
88
- self.headers = {"User-Agent": "StockResearchMVP/1.0 (educational@example.com)"} # keep original UA if desired
89
-
90
  def get_cik(self, ticker):
91
  try:
92
  time.sleep(0.5)
@@ -94,14 +81,13 @@ class SECUtils:
94
  if response.status_code != 200:
95
  return None
96
  data = response.json()
97
- for _, v in data.items():
98
  if isinstance(v, dict) and v.get('ticker', '').upper() == ticker.upper():
99
  return str(v['cik_str']).zfill(10)
100
  return None
101
  except Exception as e:
102
  print(f"CIK lookup error: {e}")
103
  return None
104
-
105
  def get_recent_filings(self, ticker):
106
  try:
107
  cik = self.get_cik(ticker)
@@ -132,6 +118,7 @@ class SECUtils:
132
  except Exception as e:
133
  return f"πŸ“„ **SEC Filings for {ticker}**\n\n❌ Error fetching SEC filings: {str(e)}\n\nπŸ’‘ Try [SEC EDGAR search](https://www.sec.gov/edgar/search/) directly."
134
 
 
135
  class NewsUtils:
136
  def __init__(self):
137
  self.headers = {"User-Agent": "StockResearchMVP/1.0 (educational@example.com)"}
@@ -154,11 +141,16 @@ class NewsUtils:
154
  except Exception as e:
155
  return f"πŸ“° **Latest News for {ticker}**\n\n❌ Error fetching news: {str(e)}\n\nπŸ’‘ Try these alternatives:\nβ€’ [Yahoo Finance News](https://finance.yahoo.com/quote/{ticker}/news)\nβ€’ [Google Finance](https://www.google.com/finance/quote/{ticker}:NASDAQ)\nβ€’ [MarketWatch](https://www.marketwatch.com/investing/stock/{ticker})"
156
 
 
157
  def get_tradingview_embed(ticker):
158
  ticker = ticker.strip().upper() if ticker else "AAPL"
159
  ticker = ''.join(filter(str.isalnum, ticker))
160
- return f'<iframe src="https://s.tradingview.com/widgetembed/?symbol={ticker}&interval=D&hidesidetoolbar=1&theme=light" width="100%" height="400" frameborder="0" allowtransparency="true" scrolling="no"></iframe>'
 
 
 
161
 
 
162
  def simulate_order_book(side, order_type, price, size, seed=123):
163
  np.random.seed(seed)
164
  base_price = 100.00
@@ -169,7 +161,12 @@ def simulate_order_book(side, order_type, price, size, seed=123):
169
  sell_mask = levels > base_price
170
  buys = np.where(buy_mask, buy_sizes, 0)
171
  sells = np.where(sell_mask, sell_sizes, 0)
172
- df = pd.DataFrame({'Price': levels, 'Buy Size': buys, 'Sell Size': sells}).sort_values(by='Price', ascending=False).reset_index(drop=True)
 
 
 
 
 
173
  fill_msg = ""
174
  if order_type == "Market":
175
  if side == "Buy":
@@ -184,8 +181,8 @@ def simulate_order_book(side, order_type, price, size, seed=123):
184
  if side == "Buy":
185
  if price >= df['Price'].min():
186
  sells_at_or_below = df[(df['Price'] <= price) & (df['Sell Size'] > 0)]
187
- if sells_at_or_below.shape:
188
- fill_price = sells_at_or_below.iloc['Price']
189
  fill_msg = f"Filled {size} @ {fill_price:.2f} (Aggressive Limit Buy)"
190
  else:
191
  queue_spot = 1 + np.random.randint(0, 3)
@@ -195,8 +192,8 @@ def simulate_order_book(side, order_type, price, size, seed=123):
195
  else:
196
  if price <= df['Price'].max():
197
  buys_at_or_above = df[(df['Price'] >= price) & (df['Buy Size'] > 0)]
198
- if buys_at_or_above.shape:
199
- fill_price = buys_at_or_above.iloc['Price']
200
  fill_msg = f"Filled {size} @ {fill_price:.2f} (Aggressive Limit Sell)"
201
  else:
202
  queue_spot = 1 + np.random.randint(0, 3)
@@ -234,6 +231,7 @@ def slippage_estimator(side, order_size, seed=123):
234
  summary = f"Est. avg fill @ {avg_fill:.2f}; Slippage: {slip:.2f} ({slip_pct:.2f}%) from ideal {base_price}"
235
  return summary, df
236
 
 
237
  sec_utils = SECUtils()
238
  news_utils = NewsUtils()
239
 
@@ -253,80 +251,33 @@ def update_stock_info(ticker):
253
  chart_html = get_tradingview_embed(ticker)
254
  return quote_data, news_data, filings_data, financial_data, chart_html
255
 
256
- # =========================================================
257
- # =============== CURRICULUM (ISOLATED ADD) ===============
258
- # =========================================================
259
-
260
- # Education calculators (names isolated from original)
261
- def edu_rr_position_size(account_equity: float, risk_pct: float, entry: float, stop: float) -> str:
262
- if account_equity <= 0 or risk_pct <= 0:
263
- return "Inputs must be positive."
264
- risk_dollars = account_equity * (risk_pct / 100.0)
265
- per_share_risk = max(1e-6, abs(entry - stop))
266
- shares = int(risk_dollars // per_share_risk)
267
- rr2_target = entry + 2 * (entry - stop) if entry > stop else entry - 2 * (stop - entry)
268
- rr3_target = entry + 3 * (entry - stop) if entry > stop else entry - 3 * (stop - entry)
269
- return (
270
- f"Risk: ${risk_dollars:,.2f}\n"
271
- f"Max shares: {shares:,}\n"
272
- f"Targets: 2R={rr2_target:.2f}, 3R={rr3_target:.2f}"
273
- )
274
-
275
- def edu_atr_stop(entry: float, atr: float, atr_mult: float, direction: str) -> str:
276
- if atr <= 0 or atr_mult <= 0:
277
- return "ATR and multiplier must be > 0."
278
- stop = entry - atr_mult * atr if direction == "Long" else entry + atr_mult * atr
279
- return f"Suggested stop: {stop:.2f}"
280
-
281
- def edu_expectancy(win_rate_pct: float, avg_win: float, avg_loss: float) -> str:
282
- p = max(0.0, min(1.0, win_rate_pct / 100.0))
283
- if avg_win < 0 or avg_loss <= 0:
284
- return "Avg win must be >= 0 and avg loss > 0."
285
- exp = p * avg_win - (1 - p) * avg_loss
286
- return f"Expectancy per trade: {exp:.2f}"
287
-
288
- def edu_risk_of_ruin(win_rate_pct: float, reward_risk: float, bankroll_risk_pct: float) -> str:
289
- p = max(0.0, min(1.0, win_rate_pct / 100.0))
290
- r = max(1e-6, reward_risk)
291
- b = max(1e-6, bankroll_risk_pct / 100.0)
292
- edge = p * r - (1 - p)
293
- if edge <= 0:
294
- return "High risk of ruin (negative/zero edge)."
295
- approx_ror = max(0.0, min(1.0, (1 - edge) ** (1 / b)))
296
- return f"Heuristic risk of ruin: {approx_ror*100:.1f}% (educational)"
297
-
298
- # =========================================================
299
- # ========================== UI ===========================
300
- # =========================================================
301
-
302
  css = """
303
  .gradio-container {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 1400px; margin: 0 auto;}
304
  .tab-nav button {font-size: 16px; font-weight: 600;}
305
  """
306
 
307
- with gr.Blocks(css=css, theme=gr.themes.Soft(), title="Bullish Minds AI - Stock Research & Education") as demo:
308
- if os.path.exists("logo.png"):
309
- gr.Image("logo.png", elem_id="header-logo", show_label=False, show_download_button=False)
310
-
311
  gr.Markdown("""
312
  # **Bullish Minds AI**
313
- Stock Research Platform MVP + Trading Strategies Curriculum
314
 
315
- Honest, educational, and data‑rich. Research with quotes, news, filings, charts; learn with interactive lessons. Not financial advice.
316
- """)
317
 
 
 
 
 
318
  with gr.Row():
319
  with gr.Column(scale=3):
320
  ticker_input = gr.Textbox(
321
  label="Stock Ticker",
322
- placeholder="Enter ticker (e.g., AAPL, TSLA, MSFT, GOOGL)",
323
  value="AAPL"
324
  )
325
  with gr.Column(scale=1):
326
- refresh_btn = gr.Button("πŸ”„ Refresh Data", variant="primary")
327
-
328
  with gr.Tabs():
329
- # -------- Research Tabs (unchanged wiring) --------
330
  with gr.TabItem("πŸ’° Quote & Overview"):
331
  quote_output = gr.Markdown(value="Enter a ticker to see stock quote")
332
  with gr.TabItem("πŸ“° News"):
@@ -339,341 +290,83 @@ Honest, educational, and data‑rich. Research with quotes, news, filings, chart
339
  gr.Markdown("### Interactive Price Chart")
340
  gr.Markdown("*Powered by TradingView*")
341
  chart_output = gr.HTML(get_tradingview_embed("AAPL"))
342
-
343
- # -------- Education Tabs (isolated) --------
344
- with gr.TabItem("πŸŽ“ Education"):
345
  with gr.Tabs():
346
- # 0) Orientation
347
- with gr.TabItem("0) Orientation"):
348
- gr.Markdown("""
349
- ## Orientation & Disclaimers
350
- - Educational purpose only; not financial advice. Trading involves risk of loss, including principal.
351
- - Suggested learning paths: Beginner β†’ Foundations + Risk/Psych β†’ Day or Swing β†’ Validation β†’ Compliance; Long‑Term track for investors.
352
- - Time commitment: Foundations (3–5 hrs), Risk/Psych (2–3 hrs), Day or Swing Track (6–12 hrs), Validation/Compliance (3–5 hrs), Capstones (varies).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  """)
354
- gr.Markdown("### How to use this curriculum")
355
- gr.Markdown("- Read the short lessons, then use calculators/simulators. Complete quick quizzes and assignments. Keep a journal and review weekly.")
356
-
357
- # 1) Market Foundations
358
- with gr.TabItem("1) Foundations"):
359
  with gr.Tabs():
360
- with gr.TabItem("1.1 Structure & Products"):
361
- gr.Markdown("""
362
- ### Market Structure & Products
363
- Why it matters: Understanding where orders go and how prices form prevents unrealistic expectations about fills and slippage.
364
-
365
- - Exchanges vs dark pools: Lit venues form NBBO; dark pools internalize flow off‑exchange. Auction (price discovery via auction book) vs dealer (market makers quote two‑sided markets).
366
- - Tickers, float, market cap, sectors, indices: Float constrains supply; sector/Index membership affects passive flows and correlations.
367
- - Products overview: Stocks (ownership), ETFs (baskets; creation/redemption), ADRs (foreign shares), options/futures (context only here).
368
- """)
369
- gr.Markdown("#### Interactive: Microstructure tools")
370
- with gr.Row():
371
- with gr.Column():
372
- gr.Interface(
373
- fn=simulate_order_book,
374
- inputs=[
375
- gr.Dropdown(["Buy", "Sell"], label="Order Side"),
376
- gr.Dropdown(["Market", "Limit"], label="Order Type"),
377
- gr.Number(value=100.00, label="Order Price (for limit)"),
378
- gr.Slider(1, 100, value=10, step=1, label="Order Size"),
379
- gr.Number(value=123, label="Seed")
380
- ],
381
- outputs=[gr.Dataframe(label="Order Book"), gr.Textbox(label="Fill Message")],
382
- live=False,
383
- allow_flagging="never"
384
- )
385
- with gr.Column():
386
- gr.Interface(
387
- fn=slippage_estimator,
388
- inputs=[
389
- gr.Dropdown(["Buy", "Sell"], label="Order Side"),
390
- gr.Slider(1, 300, value=50, step=1, label="Order Size"),
391
- gr.Number(value=123, label="Seed")
392
- ],
393
- outputs=[gr.Textbox(label="Estimate"), gr.Dataframe(label="Fill Breakdown")],
394
- live=False,
395
- allow_flagging="never"
396
- )
397
- gr.Markdown("#### Quiz")
398
- f_q1 = gr.Radio(
399
- ["Exchanges and dark pools route orders differently", "Dark pools set the NBBO", "Dealer markets have no market makers"],
400
- label="Which is true?"
401
  )
402
- f_q1_out = gr.Markdown()
403
- def f_q1_check(ans):
404
- return "Correct." if ans == "Exchanges and dark pools route orders differently" else "Review: NBBO is lit; dealers do make markets."
405
- f_q1_btn = gr.Button("Submit")
406
- f_q1_btn.click(f_q1_check, f_q1, f_q1_out)
407
-
408
- gr.Markdown("#### Assignment")
409
- gr.Markdown("- Watch the tape (L2/T&S) for a liquid ticker at open for 5 minutes; note spread, prints, and any imbalance behavior. Summarize how it would affect a market vs limit order.")
410
-
411
- with gr.TabItem("1.2 Accounts & Orders"):
412
- gr.Markdown("""
413
- ### Accounts, Brokers & Order Execution
414
- Why it matters: Order choice and account rules largely determine slippage, fills, and survivability.
415
-
416
- - Cash vs margin accounts; PDT basics (US) and leverage risks; borrow fees for shorts.
417
- - Order types: market, limit, stop, stop‑limit, trailing; OCO and bracket orders to pre‑plan exits.
418
- - Slippage, spreads, liquidity; when L2/T&S help vs harm (noise).
419
- """)
420
- gr.Markdown("#### Assignment")
421
- gr.Markdown("- For a ticker of choice, write when to use a market order vs a limit order in three scenarios: breakout, pullback, and thin after‑hours.")
422
-
423
- with gr.TabItem("1.3 Fees & Taxes"):
424
- gr.Markdown("""
425
- ### Fees, Taxes & Recordkeeping
426
- Why it matters: Friction and taxes compound; knowing costs improves net expectancy.
427
-
428
- - Commissions vs PFOF; short borrow fees; margin interest and compounding risk.
429
- - Taxes (high level): short vs long‑term gains; wash sale basics; consult a tax professional for personal advice.
430
- - Trade journal: screenshots, tags, metrics (MAE/MFE, adherence) to drive improvement.
431
- """)
432
- gr.Markdown("#### Assignment")
433
- gr.Markdown("- Create a simple trade log template with fields: ticker, setup tag, hypothesis, entry/stop/targets, size, MAE/MFE, slippage, adherence, emotions, lesson learned.")
434
-
435
- with gr.TabItem("1.4 Charts & Data"):
436
- gr.Markdown("""
437
- ### Charts, Timeframes & Data
438
- Why it matters: Multi‑timeframe context filters noise; data events explain volatility.
439
-
440
- - Candles, OHLC, volume; top‑down multi‑timeframe analysis (weeklyβ†’dailyβ†’intra).
441
- - Indicators (context): MA (trend), RSI (momentum), MACD (trend/momentum), ATR (volatility), VWAP (volume‑weighted mean).
442
- - Data drivers: economic calendar, earnings, splits, dividends and why surprises move price.
443
- """)
444
- gr.Markdown("#### Assignment")
445
- gr.Markdown("- Pick a stock. Mark weekly trend, daily key levels, and one intraday trigger you would consider. Explain why.")
446
-
447
- # 2) Risk & Psychology
448
- with gr.TabItem("2) Risk & Psychology"):
449
- with gr.Tabs():
450
- with gr.TabItem("2.1 Risk Core"):
451
- gr.Markdown("""
452
- ### Risk Management Core
453
- Why it matters: Survival precedes success; math beats hope.
454
-
455
- - Risk‑per‑trade sizing from stop distance (ATR/structure).
456
- - Reward:risk, win rate, expectancy; drawdown controls; circuit breakers and daily max loss.
457
- """)
458
- with gr.Row():
459
- with gr.Column():
460
- acct = gr.Number(label="Account Equity ($)", value=5000)
461
- riskpct = gr.Slider(0.1, 5, value=1.0, step=0.1, label="Risk per Trade (%)")
462
- entry = gr.Number(label="Entry Price", value=100.0)
463
- stop = gr.Number(label="Stop Price", value=98.0)
464
- calc_btn = gr.Button("Position Size & Targets")
465
- with gr.Column():
466
- rr_out = gr.Textbox(label="Sizing/Targets", lines=6)
467
- calc_btn.click(edu_rr_position_size, [acct, riskpct, entry, stop], rr_out)
468
-
469
- gr.Markdown("#### Expectancy")
470
- wr = gr.Slider(10, 90, value=45, step=1, label="Win Rate (%)")
471
- avg_win = gr.Number(label="Avg Win ($)", value=150)
472
- avg_loss = gr.Number(label="Avg Loss ($)", value=100)
473
- exp_btn = gr.Button("Compute Expectancy")
474
- exp_out = gr.Textbox(label="Expectancy", lines=2)
475
- exp_btn.click(edu_expectancy, [wr, avg_win, avg_loss], exp_out)
476
-
477
- gr.Markdown("#### Risk of Ruin (Heuristic)")
478
- wr2 = gr.Slider(10, 90, value=45, step=1, label="Win Rate (%)")
479
- rr_slider = gr.Slider(0.5, 3.0, value=1.5, step=0.1, label="Reward:Risk")
480
- bankrisk = gr.Slider(0.5, 10.0, value=1.0, step=0.5, label="Bankroll Risk per Trade (%)")
481
- ror_btn = gr.Button("Estimate")
482
- ror_out = gr.Textbox(label="Risk of Ruin", lines=2)
483
- ror_btn.click(edu_risk_of_ruin, [wr2, rr_slider, bankrisk], ror_out)
484
-
485
- gr.Markdown("#### Assignment")
486
- gr.Markdown("- Define personal circuit breakers: max daily loss, max trades, and stop‑trading conditions after consecutive losers. Write them into a visible checklist.")
487
-
488
- with gr.TabItem("2.2 Psychology"):
489
- gr.Markdown("""
490
- ### Trader Psychology
491
- Why it matters: Edge dies without discipline; emotions tax returns.
492
-
493
- - Biases: loss aversion, FOMO, recency; use if‑then rules to pre‑commit behavior.
494
- - Routines: pre/post‑market checklists; weekly reviews; accountability partners.
495
- - Overtrading controls: enforced timeouts, trade count limits; set alarms.
496
- """)
497
- gr.Markdown("#### Assignment")
498
- gr.Markdown("- Draft a one‑page ruleset. Include: allowed setups, entry/stop/target rules, risk per trade, when to stop trading, and review cadence.")
499
-
500
- # 3) Day Trading Track
501
- with gr.TabItem("3) Day Trading"):
502
- with gr.Tabs():
503
- with gr.TabItem("3.1 Overview"):
504
- gr.Markdown("""
505
- ### Day Trading Overview
506
- - Pros: frequent reps/feedback; Cons: noise, slippage, cognitive load.
507
- - Capital, PDT, margin constraints; ideal markets (liquid large caps, high RVOL).
508
- """)
509
- with gr.TabItem("3.2 Intraday Setups"):
510
- gr.Markdown("""
511
- ### Core Intraday Setups
512
- - ORB & Pullback: Define the first 5–15m range; trade break with tight invalidation; look for pullback to ORB top with volume confirmation.
513
- - VWAP Trend vs Reversion: Trend days ride VWAP as dynamic support/resistance; on mean‑reversion days, fades back to VWAP with strict risk.
514
- - Momentum Ignition: Identify high RVOL + catalyst; enter on reclaim of key level with tape confirming; tighten quickly.
515
- - Mean‑Reversion to PDH/PDL and Gap‑Fills: Use prior day levels as magnets; require exhaustion/taper on approach; avoid fighting strong trends.
516
- - News/Catalyst + Halts: Understand halt codes; avoid chasing first prints; trade post‑halt structure if liquidity permits.
517
- """)
518
- gr.Markdown("#### Practice")
519
- gr.Markdown("- Pick two days for a liquid ticker. Mark ORB, VWAP, PDH/PDL. Describe one A+ and one avoid trade with exact trigger/stop/targets.")
520
- with gr.TabItem("3.3 Tools & Levels"):
521
- gr.Markdown("""
522
- ### Tools & Levels
523
- - Pre‑market prep: gap scan (percent + RVOL), news filter, float awareness.
524
- - Key levels: pre‑market H/L, PDH/PDL, weekly pivots, pre‑identified inflection areas.
525
- - Tape reading: value for liquidity/urgency; ignore when it induces noise.
526
- """)
527
- with gr.TabItem("3.4 Execution & Risk"):
528
- gr.Markdown("""
529
- ### Execution & Risk
530
- - Scaling in/out, partials; dynamic stops (structure/ATR).
531
- - Hotkeys and bracket orders to reduce latency; enforce max daily loss and trade count.
532
- """)
533
- with gr.TabItem("3.5 Playbooks & Cases"):
534
- gr.Markdown("""
535
- ### Playbooks & Case Studies
536
- - Template: hypothesis, trigger, invalidation, targets, management.
537
- - Build A+ examples with screenshots and metrics (MAE/MFE, RR, adherence).
538
- """)
539
- with gr.TabItem("3.6 Metrics & Review"):
540
- gr.Markdown("""
541
- ### Metrics & Review
542
- - KPIs: expectancy, MAE/MFE, avg hold, adherence. Weekly scorecard and top‑3 fixes to guide next week.
543
- """)
544
-
545
- # 4) Swing Trading Track
546
- with gr.TabItem("4) Swing Trading"):
547
- with gr.Tabs():
548
- with gr.TabItem("4.1 Overview"):
549
- gr.Markdown("""
550
- ### Swing Trading Overview
551
- - Pros/cons vs day trading; time efficiency; accepting overnight gap risk; higher signal‑to‑noise on daily/weekly frames.
552
- """)
553
- with gr.TabItem("4.2 Core Setups"):
554
- gr.Markdown("""
555
- ### Core Swing Setups
556
- - Breakout from Base / Volatility Contraction: Tight pattern + rising RS; buy pivot with volume burst; cut below structure.
557
- - Pullback to 20/50‑MA: Trend intact; pullback on light volume; reversal bar + volume confirmation; stop under swing low.
558
- - Break of Structure / Higher‑Low Retest: Trend change; enter on HL confirming with volume; stop under HL.
559
- - Range Trading with ATR Stops: Define boundaries; enter near support with confluence; ATR‑based stop; partials mid‑range.
560
- - Earnings Season Plays: Pre‑/post‑earnings patterns; beware gaps; reduce size ahead of binary events unless strategy-specific edge.
561
- """)
562
- with gr.TabItem("4.3 Scanning & Watchlists"):
563
- gr.Markdown("""
564
- ### Scanning & Watchlists
565
- - Relative strength vs sector/index; liquidity screens; ATR filters; basic fundamental overlays (EPS growth, margins, debt load, sales acceleration).
566
- """)
567
- with gr.TabItem("4.4 Entries/Stops/Targets"):
568
- gr.Markdown("""
569
- ### Entries, Stops, and Profit Taking
570
- - Stops: structural or ATR‑based; move to breakeven only when plan says; avoid premature tightening.
571
- - Pyramid/scale techniques; partial profit frameworks (e.g., take 1/3 at 2R, trail remainder).
572
- - Managing gaps and news risk proactively.
573
- """)
574
- with gr.Row():
575
- with gr.Column():
576
- entry_s = gr.Number(label="Entry", value=50.0)
577
- atr_s = gr.Number(label="ATR", value=1.5)
578
- mult_s = gr.Slider(0.5, 5.0, value=2.0, step=0.5, label="ATR Multiplier")
579
- side_s = gr.Radio(["Long", "Short"], value="Long", label="Direction")
580
- atr_btn = gr.Button("Compute Stop")
581
- with gr.Column():
582
- atr_out = gr.Textbox(label="Stop Suggestion", lines=2)
583
- atr_btn.click(edu_atr_stop, [entry_s, atr_s, mult_s, side_s], atr_out)
584
- with gr.TabItem("4.5 Portfolio & Risk"):
585
- gr.Markdown("""
586
- ### Portfolio & Risk
587
- - Correlation/sector exposure; max concurrent names; volatility budgeting; optional options for risk shaping (protective puts/collars).
588
- """)
589
- with gr.TabItem("4.6 Review Cycle"):
590
- gr.Markdown("""
591
- ### Review Cycle
592
- - Weekly prep (Sunday routine), mid‑week check‑ins; constant playbook refinement and journaling.
593
- """)
594
-
595
- # 5) Long-Term Investing
596
- with gr.TabItem("5) Long‑Term"):
597
- with gr.Tabs():
598
- with gr.TabItem("5.1 Foundations"):
599
- gr.Markdown("""
600
- ### Investing Foundations
601
- - Time horizon, risk capacity vs tolerance; DCA vs lump sum; sequence‑of‑returns risk and staying the course.
602
- """)
603
- with gr.TabItem("5.2 Allocation & Diversification"):
604
- gr.Markdown("""
605
- ### Asset Allocation & Diversification
606
- - Core indexing (total market + international + bonds); factor tilts (value/small/quality/momentum); rebalancing rules (calendar vs threshold).
607
- """)
608
- with gr.TabItem("5.3 Equity Selection (Optional)"):
609
- gr.Markdown("""
610
- ### Equity Selection (Optional Stock‑Picking)
611
- - Business quality: moats, ROIC, FCF, balance sheet; quick valuation snapshots (PE, EV/EBITDA, DCF intuition); dividend growth strategies and payout ratios.
612
- """)
613
- with gr.TabItem("5.4 Behavior & Discipline"):
614
- gr.Markdown("""
615
- ### Behavior & Discipline
616
- - Avoid panic buy/sell; automate contributions; write an IPS (Investment Policy Statement).
617
- """)
618
- with gr.TabItem("5.5 Tax Optimization"):
619
- gr.Markdown("""
620
- ### Tax Optimization (High level)
621
- - Accounts: taxable vs tax‑advantaged; asset location basics; harvesting concepts. Consult a tax professional for personal advice.
622
- """)
623
-
624
- # 6) Strategy Validation & Development
625
- with gr.TabItem("6) Validation"):
626
- with gr.Tabs():
627
- with gr.TabItem("6.1 Back/Forward Testing"):
628
- gr.Markdown("""
629
- ### Backtesting & Forward Testing
630
- - Data quality; survivorship and look‑ahead bias; walk‑forward validation; out‑of‑sample testing; paper before real.
631
- """)
632
- with gr.TabItem("6.2 Paper β†’ Capital"):
633
- gr.Markdown("""
634
- ### Paper Trading & Phased Capital Deployment
635
- - Sim β†’ micro size β†’ scale on adherence and expectancy KPIs; cut size after rule breaches.
636
- """)
637
- with gr.TabItem("6.3 KPIs & Edge"):
638
- gr.Markdown("""
639
- ### KPIs & Edge Tracking
640
- - Define edge precisely; maintain setup playbooks; build an expectancy/KPI dashboard and review weekly.
641
- """)
642
-
643
- # 7) Compliance, Ethics & Safety
644
- with gr.TabItem("7) Compliance"):
645
- gr.Markdown("""
646
- ### Compliance, Ethics & Safety
647
- - PDT (US) overview; margin basics; short‑selling mechanics, locate/borrow fees; Reg SHO context.
648
- - Earnings and MNPI; avoid rumor/pump behavior; platform safeguards: max daily loss, time‑outs, risk limits.
649
- """)
650
-
651
- # 8) Capstones & Certifications
652
- with gr.TabItem("8) Capstones"):
653
- gr.Markdown("""
654
- ### Capstones & Certifications
655
- - Day Trading: 20 simulated trades across 3 setups with predefined risk and full journals; weekly review presentation.
656
- - Swing Trading: Manage a 5‑name swing portfolio for 6 simulated weeks; submit hypothesis plans and results.
657
- - Long‑Term: Write an IPS and backtest a DCA plan through historical drawdowns; present stress‑test responses.
658
- """)
659
-
660
- # 9) App‑Native Components
661
- with gr.TabItem("9) App‑Native"):
662
- gr.Markdown("""
663
- ### App‑Native Components (Build Plan)
664
- - Calculators: position size, expectancy, ATR stops, DCA, rebalancing.
665
- - Simulators: intraday replay, gap/open auction, earnings reaction.
666
- - Checklists: pre‑market, weekly swing review, quarterly IPS.
667
- - Dashboards: KPIs (win rate, RR, expectancy, equity curve), risk heatmap.
668
- - Journaling: trade log with tags, screenshots, reasons to enter/exit, emotions.
669
- """)
670
 
671
  gr.Markdown("""
672
  ---
673
- Data sources: Polygon (quotes), Finnhub (financials), Yahoo RSS (news), SEC EDGAR (filings), TradingView (charts).
674
- Troubleshooting: Check tickers, API keys, and retry if rate‑limited.
675
- """)
676
 
 
 
677
  ticker_input.change(
678
  fn=update_stock_info,
679
  inputs=[ticker_input],
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import requests
3
  import numpy as np
 
8
  from datetime import datetime
9
  import os
10
 
11
+ # === CONFIG: API KEYS ===
 
 
 
 
12
  POLYGON_API_KEY = os.getenv("POLYGON_API_KEY") or "fAhg47wPlf4FT6U2Hn23kQoQCQIyW0G_"
13
+ FINNHUB_API_KEY = "d2urs69r01qq994h1f5gd2urs69r01qq994h1f60"
14
 
15
+ # --- QUOTE SECTION ---
16
  def fetch_polygon_quote(ticker, polygon_api_key=POLYGON_API_KEY):
17
  url = f"https://api.polygon.io/v2/aggs/ticker/{ticker.upper()}/prev?adjusted=true&apiKey={polygon_api_key}"
18
  try:
 
20
  response.raise_for_status()
21
  data = response.json()
22
  if data.get("results"):
23
+ last = data["results"][0]
24
+ price = last["c"]
25
+ close_dt = datetime.utcfromtimestamp(last["t"] / 1000).strftime('%Y-%m-%d')
 
 
 
26
  return f"πŸ’° **Previous Close for {ticker.upper()} (as of {close_dt})**\n\nβ€’ **Close Price:** ${price:.2f}\n\n_(Free Polygon plan only provides prior close)_"
27
  else:
28
  return f"❌ Quote data unavailable for {ticker.upper()}."
29
  except Exception as e:
30
  return f"❌ Error: {str(e)}"
31
 
32
+ # --- FINNHUB FINANCIAL SUMMARY ---
33
  def get_financial_summary_finnhub(ticker, finnhub_api_key=FINNHUB_API_KEY):
34
  url = f"https://finnhub.io/api/v1/stock/metric?symbol={ticker.upper()}&metric=all&token={finnhub_api_key}"
35
  try:
36
  response = requests.get(url, timeout=10)
37
  response.raise_for_status()
38
  data = response.json()
39
+ metrics = data.get('metric', {})
40
  if not metrics:
41
  return f"πŸ“Š **Financial Summary for {ticker.upper()}**\n\n❌ No financial data found."
42
  result = f"πŸ“Š **Financial Summary for {ticker.upper()}**\n\n"
43
+ if metrics.get('totalRevenueTTM'):
44
  result += f"β€’ **Revenue (TTM):** ${int(metrics['totalRevenueTTM']):,}\n"
45
+ if metrics.get('netIncomeTTM'):
46
  result += f"β€’ **Net Income (TTM):** ${int(metrics['netIncomeTTM']):,}\n"
47
  pe = metrics.get('peNormalizedAnnual') or metrics.get('peExclExtraTTM')
48
  if pe is not None:
49
+ result += f"β€’ **P/E Ratio:** {pe:.2f}\n"
50
  pb = metrics.get('pbAnnual')
51
  if pb is not None:
52
+ result += f"β€’ **P/B Ratio:** {pb:.2f}\n"
53
  dy = metrics.get('dividendYieldIndicatedAnnual')
54
  if dy is not None:
55
+ result += f"β€’ **Dividend Yield:** {dy:.2f}%\n"
56
  dte = metrics.get('totalDebt/totalEquityAnnual')
57
  if dte is not None:
58
+ result += f"β€’ **Debt/Equity:** {dte:.2f}\n"
59
  pm = metrics.get('netProfitMarginTTM')
60
  if pm is not None:
61
+ result += f"β€’ **Net Profit Margin:** {pm:.2f}%\n"
62
  mc = metrics.get('marketCapitalization')
63
  if mc is not None:
64
  result += f"β€’ **Market Cap:** ${int(mc):,}\n"
 
68
  except Exception as e:
69
  return f"πŸ“Š **Financial Summary for {ticker.upper()}**\n\n❌ Error fetching financial summary: {e}"
70
 
71
+ # --- SEC Utilities ---
72
  class SECUtils:
73
  def __init__(self):
74
  self.cik_lookup_url = "https://www.sec.gov/files/company_tickers.json"
75
  self.edgar_search_url = "https://data.sec.gov/submissions/CIK{cik}.json"
76
+ self.headers = {"User-Agent": "StockResearchMVP/1.0 (educational@example.com)"}
 
77
  def get_cik(self, ticker):
78
  try:
79
  time.sleep(0.5)
 
81
  if response.status_code != 200:
82
  return None
83
  data = response.json()
84
+ for k, v in data.items():
85
  if isinstance(v, dict) and v.get('ticker', '').upper() == ticker.upper():
86
  return str(v['cik_str']).zfill(10)
87
  return None
88
  except Exception as e:
89
  print(f"CIK lookup error: {e}")
90
  return None
 
91
  def get_recent_filings(self, ticker):
92
  try:
93
  cik = self.get_cik(ticker)
 
118
  except Exception as e:
119
  return f"πŸ“„ **SEC Filings for {ticker}**\n\n❌ Error fetching SEC filings: {str(e)}\n\nπŸ’‘ Try [SEC EDGAR search](https://www.sec.gov/edgar/search/) directly."
120
 
121
+ # --- News Utilities ---
122
  class NewsUtils:
123
  def __init__(self):
124
  self.headers = {"User-Agent": "StockResearchMVP/1.0 (educational@example.com)"}
 
141
  except Exception as e:
142
  return f"πŸ“° **Latest News for {ticker}**\n\n❌ Error fetching news: {str(e)}\n\nπŸ’‘ Try these alternatives:\nβ€’ [Yahoo Finance News](https://finance.yahoo.com/quote/{ticker}/news)\nβ€’ [Google Finance](https://www.google.com/finance/quote/{ticker}:NASDAQ)\nβ€’ [MarketWatch](https://www.marketwatch.com/investing/stock/{ticker})"
143
 
144
+ # --- TradingView Widget Chart Embed ---
145
  def get_tradingview_embed(ticker):
146
  ticker = ticker.strip().upper() if ticker else "AAPL"
147
  ticker = ''.join(filter(str.isalnum, ticker))
148
+ return f"""
149
+ <iframe src="https://s.tradingview.com/widgetembed/?symbol={ticker}&interval=D&hidesidetoolbar=1&theme=light"
150
+ width="100%" height="400" frameborder="0" allowtransparency="true" scrolling="no"></iframe>
151
+ """
152
 
153
+ # --- LESSON TOOLS ---
154
  def simulate_order_book(side, order_type, price, size, seed=123):
155
  np.random.seed(seed)
156
  base_price = 100.00
 
161
  sell_mask = levels > base_price
162
  buys = np.where(buy_mask, buy_sizes, 0)
163
  sells = np.where(sell_mask, sell_sizes, 0)
164
+ df = pd.DataFrame({
165
+ 'Price': levels,
166
+ 'Buy Size': buys,
167
+ 'Sell Size': sells
168
+ }).sort_values(by='Price', ascending=False).reset_index(drop=True)
169
+
170
  fill_msg = ""
171
  if order_type == "Market":
172
  if side == "Buy":
 
181
  if side == "Buy":
182
  if price >= df['Price'].min():
183
  sells_at_or_below = df[(df['Price'] <= price) & (df['Sell Size'] > 0)]
184
+ if sells_at_or_below.shape[0]:
185
+ fill_price = sells_at_or_below.iloc[0]['Price']
186
  fill_msg = f"Filled {size} @ {fill_price:.2f} (Aggressive Limit Buy)"
187
  else:
188
  queue_spot = 1 + np.random.randint(0, 3)
 
192
  else:
193
  if price <= df['Price'].max():
194
  buys_at_or_above = df[(df['Price'] >= price) & (df['Buy Size'] > 0)]
195
+ if buys_at_or_above.shape[0]:
196
+ fill_price = buys_at_or_above.iloc[0]['Price']
197
  fill_msg = f"Filled {size} @ {fill_price:.2f} (Aggressive Limit Sell)"
198
  else:
199
  queue_spot = 1 + np.random.randint(0, 3)
 
231
  summary = f"Est. avg fill @ {avg_fill:.2f}; Slippage: {slip:.2f} ({slip_pct:.2f}%) from ideal {base_price}"
232
  return summary, df
233
 
234
+ # --- Instantiate Utilities ---
235
  sec_utils = SECUtils()
236
  news_utils = NewsUtils()
237
 
 
251
  chart_html = get_tradingview_embed(ticker)
252
  return quote_data, news_data, filings_data, financial_data, chart_html
253
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  css = """
255
  .gradio-container {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 1400px; margin: 0 auto;}
256
  .tab-nav button {font-size: 16px; font-weight: 600;}
257
  """
258
 
259
+ with gr.Blocks(css=css, theme=gr.themes.Soft(), title="Bullish Minds AI - Stock Research & Education Platform") as demo:
260
+ gr.Image("logo.png", elem_id="header-logo", show_label=False, show_download_button=False)
 
 
261
  gr.Markdown("""
262
  # **Bullish Minds AI**
263
+ *Stock Research Platform MVP*
264
 
265
+ **Comprehensive stock analysis, real-time data, and interactive education modules.**
 
266
 
267
+ 🎯 Enter a stock ticker symbol (**AAPL**, **TSLA**, **MSFT**, **GOOGL**) for market data, or check out the Lessons tab for learning modules!
268
+
269
+ ⚠️ **Note**: Stock quote data from Polygon (Previous Close for free plans). Financial summary from Finnhub. Charts powered by TradingView.
270
+ """)
271
  with gr.Row():
272
  with gr.Column(scale=3):
273
  ticker_input = gr.Textbox(
274
  label="Stock Ticker",
275
+ placeholder="Enter ticker (e.g., AAPL, TSLA, MSFT)",
276
  value="AAPL"
277
  )
278
  with gr.Column(scale=1):
279
+ refresh_btn = gr.Button("πŸ”„ Refresh Data", variant="primary", size="lg")
 
280
  with gr.Tabs():
 
281
  with gr.TabItem("πŸ’° Quote & Overview"):
282
  quote_output = gr.Markdown(value="Enter a ticker to see stock quote")
283
  with gr.TabItem("πŸ“° News"):
 
290
  gr.Markdown("### Interactive Price Chart")
291
  gr.Markdown("*Powered by TradingView*")
292
  chart_output = gr.HTML(get_tradingview_embed("AAPL"))
293
+ with gr.TabItem("πŸŽ“ Lessons"):
 
 
294
  with gr.Tabs():
295
+ with gr.TabItem("Lesson 1: Exchanges & Order Book"):
296
+ gr.Markdown(
297
+ """### Lesson 1 β€” Market Venues: Exchanges, Dark Pools, Auction vs. Dealer Markets
298
+ Level: Beginner β€’ Estimated time: 25–35 minutes β€’ Disclaimer: Educational only. Not financial advice.
299
+
300
+ #### Why this matters
301
+ Before you place any trade, it helps to know where your order goes and who it interacts with. Understanding market venues explains why a fill is fast or slow, why prices β€œjump,” and why a limit order protects you.
302
+
303
+ #### Learning objectives
304
+ By the end of this lesson, you will be able to:
305
+ - Define exchanges, dark pools (ATS), auction markets, and dealer markets.
306
+ - Explain how orders interact on a central limit order book versus with a dealer/market maker.
307
+ - Describe what happens during opening/closing auctions and why they matter.
308
+ - List pros/cons of lit exchanges and dark pools for typical retail traders.
309
+ - Choose a suitable order type (market vs. limit) based on venue dynamics.
310
+
311
+ #### Key terms (quick definitions)
312
+ - **Exchange (lit venue):** A registered marketplace where bids/offers are displayed ("lit") and orders match by rules (e.g., price–time priority).
313
+ - **Central Limit Order Book (CLOB):** The public queue of buy/sell limit orders at each price.
314
+ - **NBBO:** National Best Bid and Offer; the best quoted prices across exchanges (US concept).
315
+ - **Auction market:** Buyers and sellers compete by submitting orders; price emerges from order interaction (e.g., NYSE open/close auctions; continuous double auctions).
316
+ - **Dealer market:** Dealers/market makers quote both bid and ask and trade against customers (e.g., wholesalers; historically Nasdaq).
317
+ - **Market maker (MM):** A dealer obligated/encouraged to quote and provide liquidity.
318
+ - **Dark pool / ATS:** Alternative Trading System where quotes are not displayed; orders often execute at or within NBBO.
319
+ - **Price improvement:** Getting a better price than the displayed NBBO.
320
+ - **Slippage:** Getting a worse execution price than expected due to fast moves or thin liquidity.
321
+
322
+ #### [Full lesson continues...]
323
+ [...scroll for more: Big picture, scenarios, review, glossary ...]
324
+
325
+ """ + """
326
+ ---
327
+ ###### *Try the tools below to visualize order book mechanics and slippage:*
328
  """)
 
 
 
 
 
329
  with gr.Tabs():
330
+ with gr.TabItem("Order Book Simulator"):
331
+ lesson1_order = gr.Interface(
332
+ fn=simulate_order_book,
333
+ inputs=[
334
+ gr.Dropdown(["Buy", "Sell"], label="Order Side"),
335
+ gr.Dropdown(["Market", "Limit"], label="Order Type"),
336
+ gr.Number(value=100.00, label="Order Price (for limit)"),
337
+ gr.Slider(1, 100, value=10, step=1, label="Order Size"),
338
+ gr.Number(value=123, label="Seed (optional, for replay)"),
339
+ ],
340
+ outputs=[
341
+ gr.Dataframe(label="Order Book (randomized)"),
342
+ gr.Textbox(label="Result / Fill Message"),
343
+ ],
344
+ live=False,
345
+ allow_flagging="never"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  )
347
+ with gr.TabItem("Slippage Estimator"):
348
+ lesson1_slippage = gr.Interface(
349
+ fn=slippage_estimator,
350
+ inputs=[
351
+ gr.Dropdown(["Buy", "Sell"], label="Order Side"),
352
+ gr.Slider(1, 300, value=50, step=1, label="Order Size"),
353
+ gr.Number(value=123, label="Seed (for repeatability)"),
354
+ ],
355
+ outputs=[
356
+ gr.Textbox(label="Estimate"),
357
+ gr.Dataframe(label="Fill breakdown"),
358
+ ],
359
+ live=False,
360
+ allow_flagging="never"
361
+ )
362
+ # Add more lessons here as needed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
 
364
  gr.Markdown("""
365
  ---
366
+ **Data Sources:** Polygon.io for quotes, Finnhub for financials, Yahoo RSS for news, SEC EDGAR for filings.
 
 
367
 
368
+ **Troubleshooting:** If you encounter errors, double-check your ticker or wait and retry.
369
+ """)
370
  ticker_input.change(
371
  fn=update_stock_info,
372
  inputs=[ticker_input],