Spaces:
Running
Running
Update streamlit_app.py
Browse files- 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 |
-
"
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
# ๐ฐ๐ท ํ๊ตญ
|
| 39 |
-
"005930.KS": "์ผ์ฑ์ ์", "000660.KS": "SKํ์ด๋์ค", "035420.KS": "NAVER",
|
| 40 |
-
"005380.KS": "ํ๋์ฐจ", "
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
"
|
| 48 |
-
"
|
| 49 |
|
| 50 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
"2330.TW": "TSMC", "2454.TW": "MediaTek", "2317.TW": "Foxconn", "2308.TW": "Delta Elec",
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
"
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
-
# --- ๋ฐ์ด
|
| 63 |
MARKET_SAMPLES = {
|
| 64 |
-
"
|
| 65 |
-
"
|
| 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 |
-
"๐ป๐ณ ๋ฒ ํธ๋จ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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'],
|
| 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 (
|
| 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", "
|
| 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("
|
| 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 |
-
#
|
| 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("
|
| 248 |
st.dataframe(
|
| 249 |
-
df.head(
|
| 250 |
use_container_width=True,
|
| 251 |
-
column_config={"Name": "๊ธฐ์
๋ช
"
|
| 252 |
)
|
| 253 |
with c2:
|
| 254 |
-
st.error("
|
| 255 |
st.dataframe(
|
| 256 |
-
df.tail(
|
| 257 |
use_container_width=True,
|
| 258 |
-
column_config={"Name": "๊ธฐ์
๋ช
"}
|
| 259 |
)
|
| 260 |
-
else: st.warning("
|
| 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}")
|