대시보드 4개 탭 추가: 10공식 매트릭스 / 백테스트 / 미증시 동조 / 매크로·섹터

dashboard-api/main.py:
  - SCORE_ENGINE_URL/US_MARKET_URL/AUX_SIGNAL_URL 환경변수
  - _proxy_get 헬퍼
  - 신규 8개 엔드포인트:
    /api/formulas/matrix  - 10공식 신호 매트릭스 (stock_scores 직접 조회)
    /api/weights          - 학습된 공식 가중치 proxy
    /api/backtest         - 추천 백테스트 proxy
    /api/sector-concentration - 섹터 집중도 + 30% 경고 proxy
    /api/usmarket/etfs    - 미국 섹터 ETF
    /api/usmarket/pairs   - KR↔US 페어 + 60일 회귀 베타
    /api/usmarket/signals - 동조 시그널
    /api/macro/ecos       - 한국 매크로 (USD/KRW, 국고채)

dashboard-api/index.html:
  - 탭 4개 추가 (formulas/backtest/usmarket/macro)
  - renderFormulas: 매트릭스 테이블 (한글 매수/매도 신호 색상 + 툴팁)
  - renderBacktest: 7d/30d 카드 + 등급별 표 + 기간 슬라이드
  - renderUsMarket: ETF + 페어 + 시그널 3패널
  - renderMacro: KOSPI/USD-KRW/국고채 카드 + 섹터 집중도 막대 + 가중치

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kyu
2026-05-20 22:09:32 +09:00
parent 70de874ae1
commit a02d23de8d
2 changed files with 377 additions and 5 deletions
+80 -4
View File
@@ -9,10 +9,13 @@ from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, EmailStr, Field
from auth import hash_password, verify_password, create_token, current_user, dummy_verify
TA_ENGINE_URL = os.getenv("TA_ENGINE_URL", "http://ta-engine:8484")
KIS_API_URL = os.getenv("KIS_API_URL", "http://kis-api:8585")
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://ollama:11434")
CHAT_MODEL = os.getenv("CHAT_MODEL", "exaone3.5:7.8b")
TA_ENGINE_URL = os.getenv("TA_ENGINE_URL", "http://ta-engine:8484")
KIS_API_URL = os.getenv("KIS_API_URL", "http://kis-api:8585")
OLLAMA_URL = os.getenv("OLLAMA_URL", "http://ollama:11434")
SCORE_ENGINE_URL = os.getenv("SCORE_ENGINE_URL", "http://score-engine:8686")
US_MARKET_URL = os.getenv("US_MARKET_URL", "http://us-market:8383")
AUX_SIGNAL_URL = os.getenv("AUX_SIGNAL_URL", "http://aux-signal:8282")
CHAT_MODEL = os.getenv("CHAT_MODEL", "exaone3.5:7.8b")
class PositionReq(BaseModel):
code: str
@@ -1994,6 +1997,79 @@ async def calendar_api(days: int = Query(default=30, ge=1, le=180)):
"recent_dart": [dict(r) for r in recent][:30]}
async def _proxy_get(url: str, timeout: float = 10.0):
"""외부 서비스 GET 프록시 (실패 시 빈 응답)"""
try:
async with httpx.AsyncClient(timeout=timeout) as c:
r = await c.get(url)
r.raise_for_status()
return r.json()
except Exception as e:
return {"error": str(e), "source": url}
@app.get("/api/formulas/matrix")
async def formulas_matrix(limit: int = Query(default=50, ge=5, le=200)):
"""종목 × 10공식 신호 매트릭스 (오늘 스코어 기준 상위 N)"""
async with pg_pool.acquire() as c:
rows = await c.fetch("""
SELECT s.stock_code, COALESCE(d.corp_name, s.stock_name) AS stock_name,
s.total_score, s.recommendation, s.buy_votes, s.sell_votes,
s.magic_score, s.f_score, s.altman_z, s.peg, s.momentum_pct,
s.beneish_score, s.gpa_pct, s.g_score, s.amihud_illiq, s.market_beta,
s.signals, s.sector
FROM stock_scores s
LEFT JOIN dart_corps d ON d.stock_code = s.stock_code
WHERE s.score_date = (SELECT MAX(score_date) FROM stock_scores)
AND COALESCE(d.is_active, true) = true
ORDER BY s.total_score DESC
LIMIT $1
""", limit)
return [dict(r) for r in rows]
@app.get("/api/weights")
async def proxy_weights():
"""공식별 학습 가중치 (score-engine /learn-weights)"""
return await _proxy_get(f"{SCORE_ENGINE_URL}/learn-weights")
@app.get("/api/backtest")
async def proxy_backtest(days: int = Query(default=180, ge=30, le=720)):
"""추천 백테스트 (score-engine /backtest)"""
return await _proxy_get(f"{SCORE_ENGINE_URL}/backtest?days={days}", timeout=30.0)
@app.get("/api/sector-concentration")
async def proxy_sector_concentration():
"""섹터 집중도 + 30% 초과 경고 (score-engine /sector/concentration)"""
return await _proxy_get(f"{SCORE_ENGINE_URL}/sector/concentration")
@app.get("/api/usmarket/etfs")
async def proxy_us_etfs():
"""미국 섹터 ETF 목록"""
return await _proxy_get(f"{US_MARKET_URL}/etfs")
@app.get("/api/usmarket/pairs")
async def proxy_us_pairs():
"""KR↔US 페어 + 60일 회귀 베타"""
return await _proxy_get(f"{US_MARKET_URL}/pairs")
@app.get("/api/usmarket/signals")
async def proxy_us_signals():
"""미증시 동조 시그널 (전체)"""
return await _proxy_get(f"{US_MARKET_URL}/signal/latest")
@app.get("/api/macro/ecos")
async def proxy_macro_ecos():
"""한국 매크로 (aux-signal /macro/latest: USD/KRW, 국고채 10년, KOSPI)"""
return await _proxy_get(f"{AUX_SIGNAL_URL}/macro/latest")
@app.get("/")
async def index():
return FileResponse("/app/index.html")