미증시 페어 베타·시그널 결과 표시 버그 수정
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) <noreply@anthropic.com>
This commit is contained in:
@@ -1894,12 +1894,22 @@ async function renderUsMarket(){
|
|||||||
h += `<div class="panel" style="margin-top:14px"><h3>최신 동조 시그널 (${Array.isArray(signals)?signals.length:0})</h3>`;
|
h += `<div class="panel" style="margin-top:14px"><h3>최신 동조 시그널 (${Array.isArray(signals)?signals.length:0})</h3>`;
|
||||||
if(Array.isArray(signals) && signals.length){
|
if(Array.isArray(signals) && signals.length){
|
||||||
h += `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:8px">`;
|
h += `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:8px">`;
|
||||||
signals.slice(0,40).forEach(s=>{
|
// 절대값 큰 순으로 정렬
|
||||||
const score = s.signal_score!=null ? +s.signal_score : 0;
|
const sorted = signals.slice().sort((a,b)=>Math.abs(+b.total_adj||0) - Math.abs(+a.total_adj||0));
|
||||||
const c = score>0?'#69F0AE':score<0?'#FF8A80':'#90A4AE';
|
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 += `<div style="background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.05);border-radius:10px;padding:10px 12px">
|
h += `<div style="background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.05);border-radius:10px;padding:10px 12px">
|
||||||
<div style="display:flex;justify-content:space-between"><span style="color:#CFD8DC;font-family:'JetBrains Mono',monospace">${esc(s.kr_code||'-')}</span><span style="color:${c};font-weight:700;font-family:'JetBrains Mono',monospace">${score>0?'+':''}${score.toFixed(1)}</span></div>
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
||||||
<div style="font-size:10px;color:#546E7A;margin-top:4px">${esc(s.reason||'')}</div>
|
<span style="color:#CFD8DC;font-family:'JetBrains Mono',monospace;font-weight:700">${esc(s.kr_code||'-')}</span>
|
||||||
|
<span style="color:${c};font-weight:700;font-family:'JetBrains Mono',monospace">${total>0?'+':''}${total.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:10px;color:#90A4AE;margin-top:4px;display:flex;gap:10px">
|
||||||
|
<span>섹터 ${sec>=0?'+':''}${sec.toFixed(2)}</span>
|
||||||
|
<span>페어 ${pair>=0?'+':''}${pair.toFixed(2)}</span>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
h += `</div>`;
|
h += `</div>`;
|
||||||
|
|||||||
+27
-19
@@ -593,14 +593,21 @@ async def recalc_pair_betas(window_days: int = 60):
|
|||||||
if len(us_rows) < 30:
|
if len(us_rows) < 30:
|
||||||
skipped += 1
|
skipped += 1
|
||||||
continue
|
continue
|
||||||
# 한국 시계열 (stock_prices.collected_at)
|
# 한국 시계열 (stock_ohlcv 우선 — 일봉 80일+ / 폴백: stock_prices)
|
||||||
kr_rows = await conn.fetch("""
|
kr_rows = await conn.fetch("""
|
||||||
SELECT collected_at::date AS dt,
|
SELECT dt, close_price::float AS close
|
||||||
AVG(price)::float AS close
|
FROM stock_ohlcv
|
||||||
FROM stock_prices
|
WHERE stock_code=$1 AND dt >= $2 AND close_price > 0
|
||||||
WHERE stock_code=$1 AND collected_at::date >= $2
|
ORDER BY dt
|
||||||
GROUP BY collected_at::date ORDER BY dt
|
|
||||||
""", kr_c, since)
|
""", 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:
|
if len(kr_rows) < 30:
|
||||||
skipped += 1
|
skipped += 1
|
||||||
continue
|
continue
|
||||||
@@ -751,6 +758,20 @@ async def manual_signal(target: Optional[str] = None):
|
|||||||
return await calc_overnight_signals_all(d)
|
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}")
|
@app.get("/signal/{kr_code}")
|
||||||
async def get_signal(kr_code: str, days: int = Query(default=7, ge=1, le=90)):
|
async def get_signal(kr_code: str, days: int = Query(default=7, ge=1, le=90)):
|
||||||
async with pg_pool.acquire() as conn:
|
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]
|
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")
|
@app.get("/pairs")
|
||||||
async def list_pairs(kr_code: Optional[str] = None, us_ticker: Optional[str] = None):
|
async def list_pairs(kr_code: Optional[str] = None, us_ticker: Optional[str] = None):
|
||||||
async with pg_pool.acquire() as conn:
|
async with pg_pool.acquire() as conn:
|
||||||
|
|||||||
Reference in New Issue
Block a user