대시보드 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:
+80
-4
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user