이번 세션 외 그간 쌓인 변경 일괄 저장: - bareunaapi: finance_dict 금융용어 / stock_loader 종목 로더 보강 - kis-api: 키움 토큰·수집 로직 - us-market / dart-collector: 수집 보강 - docker-compose: GEMINI_API_KEY 등 환경변수 추가 - score-engine/news-collector requirements, CLAUDE.md - 신규: PROJECT.md, news-collector/sentiment_rules.py Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -829,6 +829,110 @@ async def etf_latest(etf: str):
|
||||
return dict(row) if row else {"err": "no data"}
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 미국 티커 → 한국어 라벨 (브리핑 표시용 — 비전문가도 알아보게)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
US_TICKER_LABEL = {
|
||||
"NVDA":"엔비디아 (AI반도체)","AMD":"AMD (반도체)","MU":"마이크론 (메모리반도체)",
|
||||
"INTC":"인텔 (반도체)","TSM":"TSMC (파운드리)","AVGO":"브로드컴 (반도체)",
|
||||
"QCOM":"퀄컴 (모바일칩)","TXN":"텍사스인스트루먼트 (반도체)",
|
||||
"AMAT":"어플라이드머티어리얼즈 (반도체장비)","LRCX":"램리서치 (반도체장비)",
|
||||
"KLAC":"KLA (반도체장비)",
|
||||
"AAPL":"애플","MSFT":"마이크로소프트","GOOGL":"구글(알파벳)","AMZN":"아마존",
|
||||
"META":"메타 (페이스북)","TSLA":"테슬라 (전기차)","NFLX":"넷플릭스",
|
||||
"ORCL":"오라클 (소프트웨어)","ADBE":"어도비 (소프트웨어)","CRM":"세일즈포스 (소프트웨어)",
|
||||
"JPM":"JP모건 (은행)","BAC":"뱅크오브아메리카 (은행)","GS":"골드만삭스 (증권)",
|
||||
"MS":"모건스탠리 (증권)","WFC":"웰스파고 (은행)","C":"씨티그룹 (은행)",
|
||||
"BLK":"블랙록 (자산운용)",
|
||||
"XOM":"엑슨모빌 (석유)","CVX":"셰브론 (석유)","COP":"코노코필립스 (석유)",
|
||||
"NUE":"뉴코어 (철강)","FCX":"프리포트맥모란 (구리)",
|
||||
"JNJ":"존슨앤존슨 (제약)","PFE":"화이자 (제약)","MRK":"머크 (제약)",
|
||||
"ABBV":"애브비 (제약)","LLY":"일라이릴리 (제약)","BMY":"브리스톨마이어스 (제약)",
|
||||
"GILD":"길리어드 (바이오)",
|
||||
"WMT":"월마트 (유통)","COST":"코스트코 (유통)","HD":"홈디포 (유통)",
|
||||
"MCD":"맥도날드","NKE":"나이키","SBUX":"스타벅스","DIS":"디즈니 (엔터)",
|
||||
"BA":"보잉 (항공)","LMT":"록히드마틴 (방산)","RTX":"RTX (방산)",
|
||||
"GD":"제너럴다이내믹스 (방산)","CAT":"캐터필러 (중장비)","DE":"디어 (중장비)",
|
||||
"F":"포드 (자동차)","GM":"제너럴모터스 (자동차)","TM":"도요타 (자동차)",
|
||||
"ALB":"앨버말 (리튬)","RIVN":"리비안 (전기차)",
|
||||
"DOW":"다우 (화학)","LYB":"라이온델바젤 (화학)",
|
||||
}
|
||||
ETF_KR_LABEL = {
|
||||
"SOXX":"미국 반도체 ETF","SMH":"미국 반도체 ETF","XLK":"미국 기술주 ETF",
|
||||
"QQQ":"나스닥100 ETF","XBI":"미국 바이오 ETF","IBB":"미국 바이오 ETF",
|
||||
"LIT":"글로벌 리튬·2차전지 ETF","XLE":"미국 에너지 ETF","XLF":"미국 금융 ETF",
|
||||
"XLV":"미국 헬스케어 ETF","XLI":"미국 산업재 ETF","XLP":"미국 필수소비재 ETF",
|
||||
"XLY":"미국 경기소비재 ETF","ITA":"미국 항공·방산 ETF",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/overnight-briefing")
|
||||
async def overnight_briefing(top: int = Query(default=6, ge=3, le=15)):
|
||||
"""오늘 새벽(=어젯밤 미국장) 핫/저조 종목 + 관련 KOSPI 종목 추천.
|
||||
한국어 라벨로 비전문가도 바로 이해하게 구성."""
|
||||
etf_set = set(SECTOR_ETFS.keys())
|
||||
async with pg_pool.acquire() as conn:
|
||||
latest = await conn.fetchval("SELECT MAX(trade_date) FROM us_market_daily")
|
||||
if not latest:
|
||||
return {"err": "미국 시장 데이터 없음", "hot": [], "cold": [], "sector_heat": []}
|
||||
rows = await conn.fetch("""
|
||||
SELECT ticker, change_pct FROM us_market_daily
|
||||
WHERE trade_date=$1 AND change_pct IS NOT NULL
|
||||
""", latest)
|
||||
stocks = sorted([(r["ticker"], float(r["change_pct"])) for r in rows
|
||||
if r["ticker"] not in etf_set], key=lambda x: -x[1])
|
||||
hot = [s for s in stocks if s[1] > 0.5][:top]
|
||||
cold = sorted([s for s in stocks if s[1] < -0.5], key=lambda x: x[1])[:top]
|
||||
|
||||
pair_rows = await conn.fetch("""
|
||||
SELECT p.us_ticker, p.kr_code, p.beta_60d, d.corp_name,
|
||||
s.total_score, s.recommendation
|
||||
FROM us_kr_pairs p
|
||||
JOIN dart_corps d ON d.stock_code = p.kr_code AND d.is_active = true
|
||||
LEFT JOIN (
|
||||
SELECT DISTINCT ON (stock_code) stock_code, total_score, recommendation
|
||||
FROM stock_scores ORDER BY stock_code, score_date DESC
|
||||
) s ON s.stock_code = p.kr_code
|
||||
""")
|
||||
etf_rows = await conn.fetch("""
|
||||
SELECT ticker, change_pct FROM us_market_daily
|
||||
WHERE trade_date=$1 AND ticker = ANY($2) AND change_pct IS NOT NULL
|
||||
""", latest, list(etf_set))
|
||||
|
||||
pairs_by_us: Dict[str, List] = {}
|
||||
for r in pair_rows:
|
||||
pairs_by_us.setdefault(r["us_ticker"], []).append(r)
|
||||
|
||||
def related(tk: str) -> list:
|
||||
out, seen = [], set()
|
||||
for r in pairs_by_us.get(tk, []):
|
||||
if r["kr_code"] in seen:
|
||||
continue
|
||||
seen.add(r["kr_code"])
|
||||
out.append({
|
||||
"kr_code": r["kr_code"],
|
||||
"kr_name": r["corp_name"] or r["kr_code"],
|
||||
"beta": round(float(r["beta_60d"]), 2) if r["beta_60d"] else None,
|
||||
"ai_score": round(float(r["total_score"]), 1) if r["total_score"] is not None else None,
|
||||
"recommendation": r["recommendation"] or "-",
|
||||
})
|
||||
out.sort(key=lambda x: x["ai_score"] if x["ai_score"] is not None else -999,
|
||||
reverse=True)
|
||||
return out[:4]
|
||||
|
||||
def pack(lst: list) -> list:
|
||||
return [{"ticker": t, "label": US_TICKER_LABEL.get(t, t),
|
||||
"change_pct": round(c, 2), "related_kr": related(t)} for t, c in lst]
|
||||
|
||||
sector_heat = sorted([
|
||||
{"etf": r["ticker"], "label": ETF_KR_LABEL.get(r["ticker"], r["ticker"]),
|
||||
"change_pct": round(float(r["change_pct"]), 2)} for r in etf_rows
|
||||
], key=lambda x: -x["change_pct"])
|
||||
|
||||
return {"trade_date": str(latest), "hot": pack(hot), "cold": pack(cold),
|
||||
"sector_heat": sector_heat}
|
||||
|
||||
|
||||
@app.get("/stats")
|
||||
async def stats_endpoint():
|
||||
async with pg_pool.acquire() as conn:
|
||||
|
||||
Reference in New Issue
Block a user