benjamin5607 commited on
Commit
714166b
ยท
verified ยท
1 Parent(s): 34faf8a

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +94 -60
streamlit_app.py CHANGED
@@ -8,7 +8,7 @@ from huggingface_hub import InferenceClient
8
  # 1. ํŽ˜์ด์ง€ ์„ค์ •
9
  st.set_page_config(
10
  page_title="Pocket Quant Pro",
11
- page_icon="๐Ÿ“ˆ",
12
  layout="wide",
13
  initial_sidebar_state="expanded"
14
  )
@@ -29,51 +29,89 @@ else:
29
  st.error("๐Ÿšจ ์„ค์ • ์˜ค๋ฅ˜: Secrets์— 'HF_TOKEN'์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
30
  st.stop()
31
 
32
- # --- [์—…๋ฐ์ดํŠธ] ํ‹ฐ์ปค -> ๊ธฐ์—…๋ช… ๋งคํ•‘ ๋ฆฌ์ŠคํŠธ (์ด๋ฆ„ํ‘œ) ---
33
  TICKER_NAMES = {
34
- # ๐Ÿ‡บ๐Ÿ‡ธ ๋ฏธ๊ตญ
35
- "AAPL": "Apple", "NVDA": "NVIDIA", "TSLA": "Tesla", "AMZN": "Amazon",
36
- "MSFT": "Microsoft", "GOOGL": "Google", "AMD": "AMD", "META": "Meta",
 
 
37
 
38
- # ๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ
39
- "005930.KS": "์‚ผ์„ฑ์ „์ž", "000660.KS": "SKํ•˜์ด๋‹‰์Šค", "035420.KS": "NAVER",
40
- "005380.KS": "ํ˜„๋Œ€์ฐจ", "051910.KS": "LGํ™”ํ•™", "000270.KS": "๊ธฐ์•„", "035720.KS": "์นด์นด์˜ค",
41
-
42
- # ๐Ÿ‡ฏ๐Ÿ‡ต ์ผ๋ณธ
43
- "7203.T": "Toyota", "6758.T": "Sony", "9984.T": "SoftBank",
44
- "8035.T": "Tokyo Electron", "6861.T": "Keyence", "7974.T": "Nintendo",
45
-
46
- # ๐Ÿ‡จ๐Ÿ‡ณ ์ค‘๊ตญ/ํ™์ฝฉ
47
- "9988.HK": "Alibaba", "0700.HK": "Tencent", "3690.HK": "Meituan",
48
- "1211.HK": "BYD", "1810.HK": "Xiaomi",
49
 
50
- # ๐Ÿ‡น๐Ÿ‡ผ ๋Œ€๋งŒ
 
 
 
 
 
51
  "2330.TW": "TSMC", "2454.TW": "MediaTek", "2317.TW": "Foxconn", "2308.TW": "Delta Elec",
52
-
53
- # ๐Ÿ‡ฎ๐Ÿ‡ณ ์ธ๋„
54
- "RELIANCE.NS": "Reliance Ind", "TCS.NS": "TCS", "HDFCBANK.NS": "HDFC Bank",
55
- "INFY.NS": "Infosys",
56
-
57
- # ๐Ÿ‡ป๐Ÿ‡ณ ์•„์„ธ์•ˆ
58
- "VIC": "Vingroup", "VHM": "Vinhomes", "BBCA.JK": "Bank Central Asia",
59
- "D05.SI": "DBS Group", "1155.KL": "Maybank"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  }
61
 
62
- # --- ๋ฐ์ดํ„ฐ ์ƒ˜ํ”Œ ๋ฆฌ์ŠคํŠธ ---
63
  MARKET_SAMPLES = {
64
- "๐Ÿ‡บ๐Ÿ‡ธ ๋ฏธ๊ตญ (USA)": ["AAPL", "NVDA", "TSLA", "AMZN", "MSFT", "GOOGL", "AMD"],
65
- "๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ (Korea)": ["005930.KS", "000660.KS", "035420.KS", "005380.KS", "051910.KS", "035720.KS"],
66
- "๐Ÿ‡ฏ๐Ÿ‡ต ์ผ๋ณธ (Japan)": ["7203.T", "6758.T", "9984.T", "8035.T", "6861.T", "7974.T"],
67
- "๐Ÿ‡จ๐Ÿ‡ณ ์ค‘๊ตญ/ํ™์ฝฉ": ["9988.HK", "0700.HK", "3690.HK", "1211.HK", "1810.HK"],
68
- "๐Ÿ‡น๐Ÿ‡ผ ๋Œ€๋งŒ (Taiwan)": ["2330.TW", "2454.TW", "2317.TW", "2308.TW"],
69
- "๐Ÿ‡ฎ๐Ÿ‡ณ ์ธ๋„ (India)": ["RELIANCE.NS", "TCS.NS", "HDFCBANK.NS", "INFY.NS"],
70
- "๐Ÿ‡ป๐Ÿ‡ณ ๋ฒ ํŠธ๋‚จ/์•„์„ธ์•ˆ": ["VIC", "VHM", "BBCA.JK", "D05.SI", "1155.KL"]
 
 
 
 
 
71
  }
72
 
73
  # --- ์กฑ๋ณด ๋ฐ ๊ฒ€์ƒ‰ ํ—ฌํผ ---
74
  GLOBAL_TICKER_MAP = {
75
  "TSMC": "2330.TW", "ALIBABA": "9988.HK", "RELIANCE": "RELIANCE.NS",
76
- "TOYOTA": "7203.T", "SONY": "6758.T", "SAMSUNG": "005930.KS"
 
77
  }
78
 
79
  def get_ticker_from_ai(name):
@@ -118,7 +156,7 @@ def parse_stream(stream):
118
  if hasattr(delta, 'content') and delta.content:
119
  yield delta.content.replace("```markdown", "").replace("```", "")
120
 
121
- # --- ์ฐจํŠธ ๊ทธ๋ฆฌ๊ธฐ ํ•จ์ˆ˜๋“ค ---
122
  def plot_candle_chart(hist, title):
123
  fig = go.Figure(data=[go.Candlestick(
124
  x=hist.index, open=hist['Open'], high=hist['High'], low=hist['Low'], close=hist['Close'], name="Price"
@@ -133,14 +171,12 @@ def plot_candle_chart(hist, title):
133
  def plot_bar_chart(df):
134
  colors = ['#00FF00' if x > 0 else '#FF0000' for x in df['Change(%)']]
135
  fig = go.Figure(go.Bar(
136
- x=df['Name'], # X์ถ•์„ ๊ธฐ์—…๋ช…์œผ๋กœ ํ‘œ์‹œ!
137
- y=df['Change(%)'],
138
  marker_color=colors,
139
- text=df['Change(%)'].apply(lambda x: f"{x:.2f}%"),
140
- textposition='auto'
141
  ))
142
  fig.update_layout(
143
- title="Market Heatmap (Change %)", height=300, margin=dict(l=10, r=10, t=40, b=10),
144
  template="plotly_dark", paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)'
145
  )
146
  return fig
@@ -157,12 +193,12 @@ with st.sidebar:
157
  st.caption("Data: Yahoo & Naver")
158
 
159
  # ==============================================================================
160
- # 1. ์ข…๋ชฉ ๋ถ„์„ ๋ชจ๋“œ
161
  # ==============================================================================
162
  if "์ข…๋ชฉ ๋ถ„์„" in menu:
163
  st.subheader("๐Ÿ” AI Investment Analyst")
164
  c1, c2 = st.columns([3, 1])
165
- with c1: user_input = st.text_input("Ticker", "TSMC", label_visibility="collapsed", placeholder="์ข…๋ชฉ๋ช…")
166
  with c2: analyze_btn = st.button("Go", use_container_width=True)
167
 
168
  if analyze_btn:
@@ -210,52 +246,50 @@ if "์ข…๋ชฉ ๋ถ„์„" in menu:
210
  else: status.update(label="โŒ Not Found", state="error"); st.error("Not Found")
211
 
212
  # ==============================================================================
213
- # 2. ์‹œ์žฅ ์Šค์บ๋„ˆ ๋ชจ๋“œ (๊ธฐ์—…๋ช… ์ถ”๊ฐ€๋จ)
214
  # ==============================================================================
215
  elif "์‹œ์žฅ ์Šค์บ๋„ˆ" in menu:
216
- st.subheader("๐Ÿ“ก Global Market Watch")
217
 
218
  col_sel, col_btn = st.columns([3, 1])
219
- with col_sel: target_market = st.selectbox("Market", list(MARKET_SAMPLES.keys()), label_visibility="collapsed")
220
- with col_btn: scan_trigger = st.button("Scan", use_container_width=True)
221
 
222
  if scan_trigger:
223
  tickers = MARKET_SAMPLES[target_market]
224
- with st.spinner(f"Scanning {target_market}..."):
225
  try:
 
226
  data = yf.download(tickers, period="5d", progress=False)['Close']
 
227
  if not data.empty:
228
  last = data.iloc[-1]
229
  prev = data.iloc[-2]
230
  pct = ((last - prev)/prev)*100
231
 
232
- # [์—…๋ฐ์ดํŠธ] ๊ธฐ์—…๋ช…(Name) ์ปฌ๋Ÿผ ์ถ”๊ฐ€
233
  df = pd.DataFrame({'Price': last, 'Change(%)': pct})
234
-
235
- # ์ด๋ฆ„ํ‘œ ๋”•์…”๋„ˆ๋ฆฌ์—์„œ ์ด๋ฆ„ ์ฐพ์•„์˜ค๊ธฐ (์—†์œผ๋ฉด ๊ทธ๋ƒฅ ํ‹ฐ์ปค ์‚ฌ์šฉ)
236
  df['Name'] = [TICKER_NAMES.get(x, x) for x in df.index]
237
-
238
- # ์ปฌ๋Ÿผ ์ˆœ์„œ ์ •๋ฆฌ: ์ด๋ฆ„ | ๊ฐ€๊ฒฉ | ๋“ฑ๋ฝ๋ฅ 
239
  df = df[['Name', 'Price', 'Change(%)']]
240
  df = df.dropna().sort_values('Change(%)', ascending=False)
241
 
242
- # ์ฐจํŠธ ๊ทธ๋ฆฌ๊ธฐ (๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„ X์ถ•์„ ์ด๋ฆ„์œผ๋กœ ์„ค์ •)
243
  st.plotly_chart(plot_bar_chart(df), use_container_width=True)
244
 
 
245
  c1, c2 = st.columns(2)
246
  with c1:
247
- st.success("๐Ÿš€ Top Gainers")
248
  st.dataframe(
249
- df.head(3).style.format({"Price": "{:,.2f}", "Change(%)": "{:,.2f}%"}),
250
  use_container_width=True,
251
- column_config={"Name": "๊ธฐ์—…๋ช…"} # ํ—ค๋” ์ด๋ฆ„ ์˜ˆ์˜๊ฒŒ
252
  )
253
  with c2:
254
- st.error("๐Ÿ“‰ Top Losers")
255
  st.dataframe(
256
- df.tail(3).sort_values('Change(%)').style.format({"Price": "{:,.2f}", "Change(%)": "{:,.2f}%"}),
257
  use_container_width=True,
258
- column_config={"Name": "๊ธฐ์—…๋ช…"}
259
  )
260
- else: st.warning("No data received.")
261
  except Exception as e: st.error(f"Scan failed: {e}")
 
8
  # 1. ํŽ˜์ด์ง€ ์„ค์ •
9
  st.set_page_config(
10
  page_title="Pocket Quant Pro",
11
+ page_icon="๐ŸŒ",
12
  layout="wide",
13
  initial_sidebar_state="expanded"
14
  )
 
29
  st.error("๐Ÿšจ ์„ค์ • ์˜ค๋ฅ˜: Secrets์— 'HF_TOKEN'์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
30
  st.stop()
31
 
32
+ # --- [๋Œ€๊ทœ๋ชจ ์—…๋ฐ์ดํŠธ] ํ‹ฐ์ปค -> ๊ธฐ์—…๋ช… ๋งคํ•‘ (์ฃผ์š” 8๊ฐœ๊ตญ Top 20~30 ์ปค๋ฒ„) ---
33
  TICKER_NAMES = {
34
+ # ๐Ÿ‡บ๐Ÿ‡ธ ๋ฏธ๊ตญ (Tech, Fin, Bio, Consumer)
35
+ "AAPL": "Apple", "NVDA": "NVIDIA", "TSLA": "Tesla", "AMZN": "Amazon", "MSFT": "Microsoft",
36
+ "GOOGL": "Google", "AMD": "AMD", "META": "Meta", "NFLX": "Netflix", "INTC": "Intel",
37
+ "JPM": "JPMorgan", "V": "Visa", "JNJ": "Johnson&Johnson", "WMT": "Walmart", "PG": "P&G",
38
+ "KO": "Coca-Cola", "PEP": "Pepsi", "COST": "Costco", "DIS": "Disney", "XOM": "Exxon",
39
 
40
+ # ๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ (์ฝ”์Šคํ”ผ/์ฝ”์Šค๋‹ฅ ๋Œ€์žฅ์ฃผ)
41
+ "005930.KS": "์‚ผ์„ฑ์ „์ž", "000660.KS": "SKํ•˜์ด๋‹‰์Šค", "035420.KS": "NAVER", "035720.KS": "์นด์นด์˜ค",
42
+ "005380.KS": "ํ˜„๋Œ€์ฐจ", "000270.KS": "๊ธฐ์•„", "051910.KS": "LGํ™”ํ•™", "006400.KS": "์‚ผ์„ฑSDI",
43
+ "105560.KS": "KB๊ธˆ์œต", "055550.KS": "์‹ ํ•œ์ง€์ฃผ", "005490.KS": "POSCOํ™€๋”ฉ์Šค", "068270.KS": "์…€ํŠธ๋ฆฌ์˜จ",
44
+ "207940.KS": "์‚ผ๋ฐ”", "032830.KQ": "์•Œํ…Œ์˜ค์  ", "086520.KQ": "์—์ฝ”ํ”„๋กœ", "247540.KQ": "์—์ฝ”ํ”„๋กœ๋น„์— ",
45
+
46
+ # ๐Ÿ‡ฏ๐Ÿ‡ต ์ผ๋ณธ (Nikkei 225 Major)
47
+ "7203.T": "Toyota", "6758.T": "Sony", "9984.T": "SoftBank Grp", "9434.T": "SoftBank Corp",
48
+ "8035.T": "Tokyo Elec", "6861.T": "Keyence", "7974.T": "Nintendo", "8306.T": "MUFG",
49
+ "9432.T": "NTT", "6501.T": "Hitachi", "6954.T": "Fanuc", "7267.T": "Honda",
50
+ "8316.T": "SMBC", "8411.T": "Mizuho", "6902.T": "Denso", "4063.T": "Shin-Etsu",
51
 
52
+ # ๐Ÿ‡จ๐Ÿ‡ณ ์ค‘๊ตญ/ํ™์ฝฉ (Tech & Finance)
53
+ "9988.HK": "Alibaba", "0700.HK": "Tencent", "3690.HK": "Meituan", "1211.HK": "BYD",
54
+ "1810.HK": "Xiaomi", "0941.HK": "China Mobile", "0939.HK": "CCB", "0883.HK": "CNOOC",
55
+ "2318.HK": "Ping An", "9618.HK": "JD.com", "9888.HK": "Baidu", "2015.HK": "Li Auto",
56
+
57
+ # ๐Ÿ‡น๐Ÿ‡ผ ๋Œ€๋งŒ (Semiconductor & Finance)
58
  "2330.TW": "TSMC", "2454.TW": "MediaTek", "2317.TW": "Foxconn", "2308.TW": "Delta Elec",
59
+ "2881.TW": "Fubon Fin", "2882.TW": "Cathay Fin", "1301.TW": "Formosa", "1303.TW": "Nan Ya",
60
+ "2382.TW": "Quanta", "3711.TW": "ASE Tech", "2891.TW": "CTBC Fin",
61
+
62
+ # ๐Ÿ‡ฎ๐Ÿ‡ณ ์ธ๋„ (Nifty 50)
63
+ "RELIANCE.NS": "Reliance", "TCS.NS": "TCS", "HDFCBANK.NS": "HDFC Bank", "INFY.NS": "Infosys",
64
+ "ICICIBANK.NS": "ICICI Bank", "BHARTIARTL.NS": "Bharti Airtel", "SBIN.NS": "SBI",
65
+ "HINDUNILVR.NS": "Hindustan Unilever", "ITC.NS": "ITC", "LICI.NS": "LIC India",
66
+
67
+ # ๐Ÿ‡ป๐Ÿ‡ณ ๋ฒ ํŠธ๋‚จ (VN30)
68
+ "VIC": "Vingroup", "VHM": "Vinhomes", "VCB": "Vietcombank", "VNM": "Vinamilk",
69
+ "HPG": "Hoa Phat", "GAS": "PV Gas", "MSN": "Masan", "BID": "BIDV",
70
+ "CTG": "VietinBank", "TCB": "Techcombank", "VPB": "VPBank", "MWG": "Mobile World",
71
+
72
+ # ๐Ÿ‡ฎ๐Ÿ‡ฉ ์ธ๋„๋„ค์‹œ์•„ (IDX Top)
73
+ "BBCA.JK": "BCA", "BBRI.JK": "BRI", "TLKM.JK": "Telkom", "BMRI.JK": "Mandiri",
74
+ "ASII.JK": "Astra", "UNVR.JK": "Unilever", "BBNI.JK": "BNI", "ADRO.JK": "Adaro",
75
+ "GOTO.JK": "GoTo", "AMMN.JK": "Amman Min",
76
+
77
+ # ๐Ÿ‡ธ๐Ÿ‡ฌ ์‹ฑ๊ฐ€ํฌ๋ฅด
78
+ "D05.SI": "DBS", "O39.SI": "OCBC", "U11.SI": "UOB", "Z74.SI": "Singtel",
79
+ "C52.SI": "SIA", "A17U.SI": "Ascendas", "C38U.SI": "CapitaLand", "G13.SI": "Genting",
80
+
81
+ # ๐Ÿ‡ฒ๐Ÿ‡พ ๋ง๋ ˆ์ด์‹œ์•„
82
+ "1155.KL": "Maybank", "1023.KL": "CIMB", "1295.KL": "Public Bank", "5183.KL": "Petronas Chem",
83
+ "5347.KL": "Tenaga", "6033.KL": "Petronas Gas", "4065.KL": "PPB Group",
84
+
85
+ # ๐Ÿ‡น๐Ÿ‡ญ ํƒœ๊ตญ
86
+ "PTT.BK": "PTT", "AOT.BK": "AOT", "CPALL.BK": "CP All", "ADVANC.BK": "AIS",
87
+ "SCC.BK": "Siam Cement", "BDMS.BK": "Bangkok Dusit", "KBANK.BK": "Kasikorn",
88
+
89
+ # ๐Ÿ‡ต๐Ÿ‡ญ ํ•„๋ฆฌํ•€
90
+ "SM.PS": "SM Inv", "SMPH.PS": "SM Prime", "BDO.PS": "BDO", "ALI.PS": "Ayala Land",
91
+ "BPI.PS": "BPI", "ICT.PS": "ICTSI", "JFC.PS": "Jollibee", "AC.PS": "Ayala Corp"
92
  }
93
 
94
+ # --- [๋Œ€๊ทœ๋ชจ ์—…๋ฐ์ดํŠธ] ์Šค์บ” ๋Œ€์ƒ ๋ฆฌ์ŠคํŠธ (๊ตญ๊ฐ€๋ณ„ 20~30๊ฐœ) ---
95
  MARKET_SAMPLES = {
96
+ "๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ (Korea)": ["005930.KS", "000660.KS", "035420.KS", "035720.KS", "005380.KS", "000270.KS", "051910.KS", "006400.KS", "105560.KS", "055550.KS", "005490.KS", "068270.KS", "207940.KS", "032830.KQ", "086520.KQ", "247540.KQ"],
97
+ "๐Ÿ‡บ๐Ÿ‡ธ ๋ฏธ๊ตญ (USA)": ["AAPL", "NVDA", "TSLA", "AMZN", "MSFT", "GOOGL", "AMD", "META", "NFLX", "INTC", "JPM", "V", "JNJ", "WMT", "PG", "KO", "PEP", "COST", "DIS", "XOM"],
98
+ "๐Ÿ‡ฏ๐Ÿ‡ต ์ผ๋ณธ (Japan)": ["7203.T", "6758.T", "9984.T", "9434.T", "8035.T", "6861.T", "7974.T", "8306.T", "9432.T", "6501.T", "6954.T", "7267.T", "8316.T", "8411.T", "6902.T", "4063.T"],
99
+ "๐Ÿ‡จ๐Ÿ‡ณ ์ค‘๊ตญ/ํ™์ฝฉ": ["9988.HK", "0700.HK", "3690.HK", "1211.HK", "1810.HK", "0941.HK", "0939.HK", "0883.HK", "2318.HK", "9618.HK", "9888.HK", "2015.HK"],
100
+ "๐Ÿ‡น๐Ÿ‡ผ ๋Œ€๋งŒ (Taiwan)": ["2330.TW", "2454.TW", "2317.TW", "2308.TW", "2881.TW", "2882.TW", "1301.TW", "1303.TW", "2382.TW", "3711.TW", "2891.TW"],
101
+ "๐Ÿ‡ฎ๐Ÿ‡ณ ์ธ๋„ (India)": ["RELIANCE.NS", "TCS.NS", "HDFCBANK.NS", "INFY.NS", "ICICIBANK.NS", "BHARTIARTL.NS", "SBIN.NS", "HINDUNILVR.NS", "ITC.NS", "LICI.NS"],
102
+ "๐Ÿ‡ป๐Ÿ‡ณ ๋ฒ ํŠธ๋‚จ": ["VIC", "VHM", "VCB", "VNM", "HPG", "GAS", "MSN", "BID", "CTG", "TCB", "VPB", "MWG"],
103
+ "๐Ÿ‡ฎ๐Ÿ‡ฉ ์ธ๋„๋„ค์‹œ์•„": ["BBCA.JK", "BBRI.JK", "TLKM.JK", "BMRI.JK", "ASII.JK", "UNVR.JK", "BBNI.JK", "ADRO.JK", "GOTO.JK", "AMMN.JK"],
104
+ "๐Ÿ‡ธ๐Ÿ‡ฌ ์‹ฑ๊ฐ€ํฌ๋ฅด": ["D05.SI", "O39.SI", "U11.SI", "Z74.SI", "C52.SI", "A17U.SI", "C38U.SI", "G13.SI"],
105
+ "๐Ÿ‡ฒ๐Ÿ‡พ ๋ง๋ ˆ์ด์‹œ์•„": ["1155.KL", "1023.KL", "1295.KL", "5183.KL", "5347.KL", "6033.KL", "4065.KL"],
106
+ "๐Ÿ‡น๐Ÿ‡ญ ํƒœ๊ตญ": ["PTT.BK", "AOT.BK", "CPALL.BK", "ADVANC.BK", "SCC.BK", "BDMS.BK", "KBANK.BK"],
107
+ "๐Ÿ‡ต๐Ÿ‡ญ ํ•„๋ฆฌํ•€": ["SM.PS", "SMPH.PS", "BDO.PS", "ALI.PS", "BPI.PS", "ICT.PS", "JFC.PS", "AC.PS"]
108
  }
109
 
110
  # --- ์กฑ๋ณด ๋ฐ ๊ฒ€์ƒ‰ ํ—ฌํผ ---
111
  GLOBAL_TICKER_MAP = {
112
  "TSMC": "2330.TW", "ALIBABA": "9988.HK", "RELIANCE": "RELIANCE.NS",
113
+ "TOYOTA": "7203.T", "SONY": "6758.T", "SAMSUNG": "005930.KS",
114
+ "VINFAST": "VFS", "COUPANG": "CPNG", "GRAB": "GRAB", "SHOPEE": "SE"
115
  }
116
 
117
  def get_ticker_from_ai(name):
 
156
  if hasattr(delta, 'content') and delta.content:
157
  yield delta.content.replace("```markdown", "").replace("```", "")
158
 
159
+ # --- ์ฐจํŠธ ๊ทธ๋ฆฌ๊ธฐ ---
160
  def plot_candle_chart(hist, title):
161
  fig = go.Figure(data=[go.Candlestick(
162
  x=hist.index, open=hist['Open'], high=hist['High'], low=hist['Low'], close=hist['Close'], name="Price"
 
171
  def plot_bar_chart(df):
172
  colors = ['#00FF00' if x > 0 else '#FF0000' for x in df['Change(%)']]
173
  fig = go.Figure(go.Bar(
174
+ x=df['Name'], y=df['Change(%)'],
 
175
  marker_color=colors,
176
+ text=df['Change(%)'].apply(lambda x: f"{x:.2f}%"), textposition='auto'
 
177
  ))
178
  fig.update_layout(
179
+ title="Market Heatmap (Ranked)", height=350, margin=dict(l=10, r=10, t=40, b=10),
180
  template="plotly_dark", paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)'
181
  )
182
  return fig
 
193
  st.caption("Data: Yahoo & Naver")
194
 
195
  # ==============================================================================
196
+ # 1. ์ข…๋ชฉ ๋ถ„์„
197
  # ==============================================================================
198
  if "์ข…๋ชฉ ๋ถ„์„" in menu:
199
  st.subheader("๐Ÿ” AI Investment Analyst")
200
  c1, c2 = st.columns([3, 1])
201
+ with c1: user_input = st.text_input("Ticker", "Vingroup", label_visibility="collapsed", placeholder="์ข…๋ชฉ๋ช…")
202
  with c2: analyze_btn = st.button("Go", use_container_width=True)
203
 
204
  if analyze_btn:
 
246
  else: status.update(label="โŒ Not Found", state="error"); st.error("Not Found")
247
 
248
  # ==============================================================================
249
+ # 2. ์‹œ์žฅ ์Šค์บ๋„ˆ (Top 10 / Bottom 10 ํ™•์žฅ)
250
  # ==============================================================================
251
  elif "์‹œ์žฅ ์Šค์บ๋„ˆ" in menu:
252
+ st.subheader("๐Ÿ“ก Global Market Watch (Top 10)")
253
 
254
  col_sel, col_btn = st.columns([3, 1])
255
+ with col_sel: target_market = st.selectbox("Select Country", list(MARKET_SAMPLES.keys()), label_visibility="collapsed")
256
+ with col_btn: scan_trigger = st.button("Scan Market", use_container_width=True)
257
 
258
  if scan_trigger:
259
  tickers = MARKET_SAMPLES[target_market]
260
+ with st.spinner(f"Scanning {len(tickers)} major stocks in {target_market}..."):
261
  try:
262
+ # 20~30๊ฐœ ์ข…๋ชฉ Bulk Download
263
  data = yf.download(tickers, period="5d", progress=False)['Close']
264
+
265
  if not data.empty:
266
  last = data.iloc[-1]
267
  prev = data.iloc[-2]
268
  pct = ((last - prev)/prev)*100
269
 
 
270
  df = pd.DataFrame({'Price': last, 'Change(%)': pct})
 
 
271
  df['Name'] = [TICKER_NAMES.get(x, x) for x in df.index]
 
 
272
  df = df[['Name', 'Price', 'Change(%)']]
273
  df = df.dropna().sort_values('Change(%)', ascending=False)
274
 
275
+ # ํžˆํŠธ๋งต (์ „์ฒด ํ๋ฆ„)
276
  st.plotly_chart(plot_bar_chart(df), use_container_width=True)
277
 
278
+ # ๐Ÿ”ฅ Top 10 Gainers & ๐Ÿ’ง Bottom 10 Losers
279
  c1, c2 = st.columns(2)
280
  with c1:
281
+ st.success(f"๐Ÿ”ฅ {target_market} ์ƒ์Šน Top 10")
282
  st.dataframe(
283
+ df.head(10).style.format({"Price": "{:,.2f}", "Change(%)": "{:,.2f}%"}),
284
  use_container_width=True,
285
+ column_config={"Name": "๊ธฐ์—…๋ช…", "Change(%)": "๋“ฑ๋ฝ๋ฅ "}
286
  )
287
  with c2:
288
+ st.error(f"๐Ÿ’ง {target_market} ํ•˜๋ฝ Top 10")
289
  st.dataframe(
290
+ df.tail(10).sort_values('Change(%)').style.format({"Price": "{:,.2f}", "Change(%)": "{:,.2f}%"}),
291
  use_container_width=True,
292
+ column_config={"Name": "๊ธฐ์—…๋ช…", "Change(%)": "๋“ฑ๋ฝ๋ฅ "}
293
  )
294
+ else: st.warning("๋ฐ์ดํ„ฐ ์ˆ˜์‹  ์‹คํŒจ. (ํœด์žฅ์ผ ์ˆ˜ ์žˆ์Œ)")
295
  except Exception as e: st.error(f"Scan failed: {e}")