diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index 6948542..0770392 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -149,7 +149,7 @@ export interface WatchlistItem { export interface IndexQuote { name: string - value: number + price: number change: number changeRate: number } diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 71ef64b..3ceab12 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -39,16 +39,16 @@ export function formatDate(iso: string): string { return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}` } -// 전일대비 부호 문자 (2=상승, 3=보합, 5=하락) +// 전일대비 부호 문자 (1=상한가, 2=상승, 3=보합, 4=하한가, 5=하락) export function sigToArrow(sig: string): string { - if (sig === '2') return '▲' - if (sig === '5') return '▼' + if (sig === '1' || sig === '2') return '▲' + if (sig === '4' || sig === '5') return '▼' return '-' } // 전일대비 부호에 따른 클래스 export function sigClass(sig: string): string { - if (sig === '2') return 'text-red-400' - if (sig === '5') return 'text-blue-400' + if (sig === '1' || sig === '2') return 'text-red-400' + if (sig === '4' || sig === '5') return 'text-blue-400' return 'text-gray-400' } diff --git a/frontend/src/routes/(app)/+page.svelte b/frontend/src/routes/(app)/+page.svelte index 58c2bf1..2b03475 100644 --- a/frontend/src/routes/(app)/+page.svelte +++ b/frontend/src/routes/(app)/+page.svelte @@ -296,7 +296,7 @@ {#each indices as idx}
{idx.name}
-
{idx.value.toLocaleString('ko-KR', { maximumFractionDigits: 2 })}
+
{idx.price.toLocaleString('ko-KR', { maximumFractionDigits: 2 })}
{formatRate(idx.changeRate)}
{/each} diff --git a/frontend/src/routes/(app)/kospi200/+page.svelte b/frontend/src/routes/(app)/kospi200/+page.svelte index 2b8c68a..4fe28e8 100644 --- a/frontend/src/routes/(app)/kospi200/+page.svelte +++ b/frontend/src/routes/(app)/kospi200/+page.svelte @@ -97,7 +97,7 @@ {stock.curPrc.toLocaleString()} - {sigToArrow(stock.predPreSig)} {stock.predPre.toLocaleString()} + {sigToArrow(stock.predPreSig)} {Math.abs(stock.predPre).toLocaleString()} {formatRate(stock.fluRt)} diff --git a/frontend/src/routes/(app)/theme/+page.svelte b/frontend/src/routes/(app)/theme/+page.svelte index ddb0d3e..3598dc5 100644 --- a/frontend/src/routes/(app)/theme/+page.svelte +++ b/frontend/src/routes/(app)/theme/+page.svelte @@ -190,7 +190,7 @@ {stock.curPrc.toLocaleString()} - {sigToArrow(stock.fluSig)}{stock.predPre.toLocaleString()} + {sigToArrow(stock.fluSig)}{Math.abs(stock.predPre).toLocaleString()} {formatRate(stock.fluRt)} diff --git a/services/kiwoom_service.go b/services/kiwoom_service.go index f63dab6..a88338f 100644 --- a/services/kiwoom_service.go +++ b/services/kiwoom_service.go @@ -219,12 +219,11 @@ func (k *KiwoomClient) fetchPrice(stkCd string) (*models.StockPrice, error) { if result.ReturnCode != 0 { return nil, fmt.Errorf("현재가 조회 실패: %s", result.ReturnMsg) } - price := &models.StockPrice{ Code: displayCode, Name: result.StkNm, CurrentPrice: absParseIntSafe(result.CurPrc), - ChangePrice: parseIntSafe(result.PredPre), + ChangePrice: signedChange(result.PredPre, result.FluRt), ChangeRate: parseFloatSafe(result.FluRt), Volume: absParseIntSafe(result.TrdeQty), High: absParseIntSafe(result.HighPric), @@ -451,7 +450,7 @@ func (k *KiwoomClient) GetTopVolumeStocks(market string, count int) ([]models.St Code: row.StkCd, Name: row.StkNm, CurrentPrice: absParseIntSafe(row.CurPrc), - ChangePrice: parseIntSafe(row.PredPre), + ChangePrice: signedChange(row.PredPre, row.FluRt), ChangeRate: parseFloatSafe(row.FluRt), Volume: absParseIntSafe(row.TrdeQty), Market: mktName, @@ -559,7 +558,7 @@ func (k *KiwoomClient) GetTopFluctuation(market string, ascending bool, count in Code: row.StkCd, Name: row.StkNm, CurrentPrice: absParseIntSafe(row.CurPrc), - ChangePrice: parseIntSafe(row.PredPre), + ChangePrice: signedChange(row.PredPre, row.FluRt), ChangeRate: parseFloatSafe(row.FluRt), Volume: absParseIntSafe(row.NowTrdeQty), CntrStr: parseFloatSafe(row.CntrStr), @@ -588,6 +587,27 @@ func absParseIntSafe(s string) int64 { return n } +// signedChange 전일대비를 절댓값으로 파싱 후 등락률 부호에 맞춰 부호 결정 +// 키움 API의 pred_pre 필드는 가격 필드처럼 부호가 방향 표시용이므로 flu_rt 기준으로 부호 결정 +func signedChange(predPre string, fluRt string) int64 { + n := absParseIntSafe(predPre) + rate := parseFloatSafe(fluRt) + if rate < 0 { + return -n + } + return n +} + +// signedChangeBySig 전일대비를 절댓값으로 파싱 후 flu_sig(등락기호) 기준으로 부호 결정 +// flu_sig: 1=상한가, 2=상승, 3=보합, 4=하한가, 5=하락 +func signedChangeBySig(predPre string, fluSig string) int64 { + n := absParseIntSafe(predPre) + if fluSig == "4" || fluSig == "5" { + return -n + } + return n +} + func parseFloatSafe(s string) float64 { s = strings.ReplaceAll(s, ",", "") s = strings.TrimPrefix(s, "+") diff --git a/services/kiwoom_ws_service.go b/services/kiwoom_ws_service.go index 44997a6..d5cc5f4 100644 --- a/services/kiwoom_ws_service.go +++ b/services/kiwoom_ws_service.go @@ -434,11 +434,17 @@ func (k *KiwoomWSClient) reconnect() { func parseRealPrice(code string, v map[string]string) *models.StockPrice { normalized := strings.TrimPrefix(code, "A") normalized = strings.SplitN(normalized, "_", 2)[0] // _NX, _AL 등 접미사 제거 + // 전일대비(v["11"])는 절댓값으로 파싱 후, 등락률(v["12"]) 부호에 맞춰 부호 결정 + changeAbs := absInt(parseWSInt(v["11"])) + changeRate := parseWSFloat(v["12"]) + if changeRate < 0 { + changeAbs = -changeAbs + } return &models.StockPrice{ Code: normalized, CurrentPrice: absInt(parseWSInt(v["10"])), - ChangePrice: parseWSInt(v["11"]), - ChangeRate: parseWSFloat(v["12"]), + ChangePrice: changeAbs, + ChangeRate: changeRate, Volume: absInt(parseWSInt(v["13"])), TradeMoney: absInt(parseWSInt(v["14"])), TradeVolume: absInt(parseWSInt(v["15"])), @@ -458,11 +464,17 @@ func parseRealPrice(code string, v map[string]string) *models.StockPrice { func parseExpectedPrice(code string, v map[string]string) *models.StockPrice { normalized := strings.TrimPrefix(code, "A") normalized = strings.SplitN(normalized, "_", 2)[0] + // 전일대비(v["11"])는 절댓값으로 파싱 후, 등락률(v["12"]) 부호에 맞춰 부호 결정 + expChangeAbs := absInt(parseWSInt(v["11"])) + expChangeRate := parseWSFloat(v["12"]) + if expChangeRate < 0 { + expChangeAbs = -expChangeAbs + } return &models.StockPrice{ Code: normalized, CurrentPrice: absInt(parseWSInt(v["10"])), - ChangePrice: parseWSInt(v["11"]), - ChangeRate: parseWSFloat(v["12"]), + ChangePrice: expChangeAbs, + ChangeRate: expChangeRate, TradeVolume: absInt(parseWSInt(v["15"])), Volume: absInt(parseWSInt(v["13"])), TradeTime: v["20"], diff --git a/services/kospi200_service.go b/services/kospi200_service.go index 47b1d9f..55ba625 100644 --- a/services/kospi200_service.go +++ b/services/kospi200_service.go @@ -85,7 +85,7 @@ func (s *Kospi200Service) GetStocks() ([]models.Kospi200Stock, error) { Name: s.StkNm, CurPrc: absParseIntSafe(s.CurPrc), PredPreSig: s.PredPreSig, - PredPre: parseIntSafe(s.PredPre), + PredPre: signedChangeBySig(s.PredPre, s.PredPreSig), FluRt: parseFloatSafe(s.FluRt), Volume: absParseIntSafe(s.NowTrdeQty), Open: absParseIntSafe(s.OpenPric), diff --git a/services/theme_service.go b/services/theme_service.go index 2fa7932..bb41635 100644 --- a/services/theme_service.go +++ b/services/theme_service.go @@ -109,8 +109,8 @@ func (s *ThemeService) GetThemeStocks(themeCode, dateTp string) (*models.ThemeDe } var result struct { - FluRt string `json:"flu_rt"` - DtPrftRt string `json:"dt_prft_rt"` + FluRt string `json:"flu_rt"` + DtPrftRt string `json:"dt_prft_rt"` ThemaCompStk []struct { StkCd string `json:"stk_cd"` StkNm string `json:"stk_nm"` @@ -136,7 +136,7 @@ func (s *ThemeService) GetThemeStocks(themeCode, dateTp string) (*models.ThemeDe Name: s.StkNm, CurPrc: absParseIntSafe(s.CurPrc), FluSig: s.FluSig, - PredPre: parseIntSafe(s.PredPre), + PredPre: signedChangeBySig(s.PredPre, s.FluSig), FluRt: parseFloatSafe(strings.TrimPrefix(s.FluRt, "+")), }) }