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: