chore: 누적 미커밋 작업분 일괄 커밋
trading/pipeline/head This commit looks good

이번 세션 외 그간 쌓인 변경 일괄 저장:
- 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:
kyu
2026-06-02 01:24:16 +09:00
parent 8b200e6cc6
commit 97cf5aecb0
11 changed files with 1105 additions and 52 deletions
+104
View File
@@ -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: