From 324c7a4b950d527cbd6d4b34b975db836b6e41df Mon Sep 17 00:00:00 2001 From: kyu Date: Wed, 20 May 2026 22:25:44 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AF=B8=EC=A6=9D=EC=8B=9C=20=ED=8E=98?= =?UTF-8?q?=EC=96=B4=20=EB=B2=A0=ED=83=80=C2=B7=EC=8B=9C=EA=B7=B8=EB=84=90?= =?UTF-8?q?=20=EA=B2=B0=EA=B3=BC=20=ED=91=9C=EC=8B=9C=20=EB=B2=84=EA=B7=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit us-market/main.py: 1) /signal/latest 라우트 순서 버그 수정 - 기존: /signal/{kr_code} 가 먼저 선언되어 'latest'를 코드로 매칭 → 항상 [] - 수정: /signal/latest 를 path-param 라우트보다 위로 이동 2) pair beta 재계산이 stock_prices(7~10일치)에서 데이터 부족으로 32건 전부 skip - stock_ohlcv(81일치) 우선 조회, 폴백으로 stock_prices 유지 - 결과: updated 0→32, 베타·상관 정상 산출 dashboard-api/index.html: - 미증시 시그널 카드 필드명 수정 (signal_score → total_adj/sector_adj/pair_adj) - 절대값 큰 순 정렬 + 섹터/페어 기여도 분해 표시 Co-Authored-By: Claude Opus 4.7 (1M context) --- dashboard-api/index.html | 20 ++++++++++++----- us-market/main.py | 46 +++++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/dashboard-api/index.html b/dashboard-api/index.html index 356bcb6..76d0001 100644 --- a/dashboard-api/index.html +++ b/dashboard-api/index.html @@ -1894,12 +1894,22 @@ async function renderUsMarket(){ h += `

최신 동조 시그널 (${Array.isArray(signals)?signals.length:0})

`; if(Array.isArray(signals) && signals.length){ h += `
`; - signals.slice(0,40).forEach(s=>{ - const score = s.signal_score!=null ? +s.signal_score : 0; - const c = score>0?'#69F0AE':score<0?'#FF8A80':'#90A4AE'; + // 절대값 큰 순으로 정렬 + const sorted = signals.slice().sort((a,b)=>Math.abs(+b.total_adj||0) - Math.abs(+a.total_adj||0)); + sorted.slice(0,60).forEach(s=>{ + const total = +s.total_adj || 0; + const sec = +s.sector_adj || 0; + const pair = +s.pair_adj || 0; + const c = total>0?'#69F0AE':total<0?'#FF8A80':'#90A4AE'; h += `
-
${esc(s.kr_code||'-')}${score>0?'+':''}${score.toFixed(1)}
-
${esc(s.reason||'')}
+
+ ${esc(s.kr_code||'-')} + ${total>0?'+':''}${total.toFixed(2)} +
+
+ 섹터 ${sec>=0?'+':''}${sec.toFixed(2)} + 페어 ${pair>=0?'+':''}${pair.toFixed(2)} +
`; }); h += `
`; diff --git a/us-market/main.py b/us-market/main.py index c36f979..aed16b9 100644 --- a/us-market/main.py +++ b/us-market/main.py @@ -593,14 +593,21 @@ async def recalc_pair_betas(window_days: int = 60): if len(us_rows) < 30: skipped += 1 continue - # 한국 시계열 (stock_prices.collected_at) + # 한국 시계열 (stock_ohlcv 우선 — 일봉 80일+ / 폴백: stock_prices) kr_rows = await conn.fetch(""" - SELECT collected_at::date AS dt, - AVG(price)::float AS close - FROM stock_prices - WHERE stock_code=$1 AND collected_at::date >= $2 - GROUP BY collected_at::date ORDER BY dt + SELECT dt, close_price::float AS close + FROM stock_ohlcv + WHERE stock_code=$1 AND dt >= $2 AND close_price > 0 + ORDER BY dt """, kr_c, since) + if len(kr_rows) < 30: + kr_rows = await conn.fetch(""" + SELECT collected_at::date AS dt, + AVG(price)::float AS close + FROM stock_prices + WHERE stock_code=$1 AND collected_at::date >= $2 + GROUP BY collected_at::date ORDER BY dt + """, kr_c, since) if len(kr_rows) < 30: skipped += 1 continue @@ -751,6 +758,20 @@ async def manual_signal(target: Optional[str] = None): return await calc_overnight_signals_all(d) +@app.get("/signal/latest") +async def latest_signals(limit: int = Query(default=50, le=500)): + """주의: /signal/{kr_code}보다 먼저 선언해야 함 — 안 그러면 'latest'를 kr_code로 매칭.""" + async with pg_pool.acquire() as conn: + rows = await conn.fetch(""" + SELECT DISTINCT ON (kr_code) + kr_code, signal_date, sector_adj, pair_adj, total_adj + FROM us_overnight_signal + ORDER BY kr_code, signal_date DESC + LIMIT $1 + """, limit) + return [dict(r) for r in rows] + + @app.get("/signal/{kr_code}") async def get_signal(kr_code: str, days: int = Query(default=7, ge=1, le=90)): async with pg_pool.acquire() as conn: @@ -763,19 +784,6 @@ async def get_signal(kr_code: str, days: int = Query(default=7, ge=1, le=90)): return [dict(r) for r in rows] -@app.get("/signal/latest") -async def latest_signals(limit: int = Query(default=50, le=500)): - async with pg_pool.acquire() as conn: - rows = await conn.fetch(""" - SELECT DISTINCT ON (kr_code) - kr_code, signal_date, sector_adj, pair_adj, total_adj - FROM us_overnight_signal - ORDER BY kr_code, signal_date DESC - LIMIT $1 - """, limit) - return [dict(r) for r in rows] - - @app.get("/pairs") async def list_pairs(kr_code: Optional[str] = None, us_ticker: Optional[str] = None): async with pg_pool.acquire() as conn: