주가 표시형식 수정

This commit is contained in:
Macbook
2026-06-06 00:56:45 +09:00
parent 1d4e606f77
commit 94d3f92bb9
18 changed files with 156 additions and 57 deletions
+2 -1
View File
@@ -10,6 +10,7 @@ import {
type ProcessMonitorDto,
type SystemMonitorDto,
} from '../utils/backendApi';
import { formatUpbitKrwPrice } from '../utils/safeFormat';
import DashboardGauge from './dashboard/DashboardGauge';
import DashboardSegmentBar from './dashboard/DashboardSegmentBar';
import DashboardSparkChart from './dashboard/DashboardSparkChart';
@@ -339,7 +340,7 @@ const DashboardPage: React.FC<Props> = ({ theme, onGoChart }) => {
<button type="button" className="gc-signal-main" onClick={() => onGoChart?.(s.market)}>
<strong>{buy ? 'BUY' : 'SELL'}</strong>
<span>{s.market.replace('KRW-', '')}</span>
<span className="gc-signal-price">@ {s.price?.toLocaleString('ko-KR')}</span>
<span className="gc-signal-price">@ {s.price != null ? formatUpbitKrwPrice(s.price) : ''}</span>
</button>
<span className="gc-signal-time">{s.createdAt?.slice(11, 16) ?? ''}</span>
</li>
+2 -1
View File
@@ -2,6 +2,7 @@ import React, { useRef, useEffect, useCallback, useState } from 'react';
import type { ChartManager } from '../utils/ChartManager';
import type { Drawing, DrawingPoint, DrawingToolId, Theme } from '../types';
import { formatUnixDateTime } from '../utils/timezone';
import { formatUpbitKrwPrice } from '../utils/safeFormat';
// ─── Hit-test ──────────────────────────────────────────────────────────────
const HIT_RADIUS = 7; // px (CSS 좌표)
@@ -1088,7 +1089,7 @@ function renderInfoLine(ctx: CanvasRenderingContext2D, d: Drawing, m: ChartManag
ctx.strokeStyle = d.color; ctx.lineWidth = d.lineWidth ?? 1;
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(cssW, y); ctx.stroke();
const price = d.points[0].price;
const label = price >= 1000 ? price.toLocaleString('ko-KR') : price.toFixed(4);
const label = formatUpbitKrwPrice(price);
ctx.font = 'bold 10px sans-serif';
const tw = ctx.measureText(label).width + 8;
ctx.fillStyle = d.color;
+2 -6
View File
@@ -13,6 +13,7 @@ import {
getFavorites, saveFavorites, toggleFavorite,
getHoldings, addHolding, removeHolding,
} from '../utils/marketStorage';
import { formatUpbitKrwPrice } from '../utils/safeFormat';
// ─── 타입 ──────────────────────────────────────────────────────────────────
type Tab = 'krw' | 'btc' | 'hold' | 'fav';
type SortKey = 'name' | 'price' | 'change';
@@ -29,12 +30,7 @@ export interface MarketPanelProps {
// ─── 가격 포맷 헬퍼 ────────────────────────────────────────────────────────
function fmtKrw(price: number | null | undefined): string {
if (price == null || !isFinite(price)) return '-';
if (price >= 1_000_000) return price.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
if (price >= 1_000) return price.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
if (price >= 1) return price.toLocaleString('ko-KR', { maximumFractionDigits: 2 });
if (price >= 0.01) return price.toFixed(4);
return price.toFixed(8);
return formatUpbitKrwPrice(price, '-');
}
function fmtUsd(krwPrice: number | null | undefined, rate: number): string {
+2 -3
View File
@@ -7,12 +7,11 @@ import { formatNowClock, useDisplayTimezone } from '../utils/timezone';
import type { OrderbookDisplayUnit, WsStatus } from '../hooks/useUpbitOrderbook';
import type { RecentTrade } from '../hooks/useUpbitRecentTrades';
import { getKoreanName } from '../utils/marketNameCache';
import { formatUpbitKrwPrice } from '../utils/safeFormat';
function fmtPrice(p: number): string {
if (p == null || !isFinite(p)) return '-';
if (p >= 1_000) return p.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
if (p >= 1) return p.toFixed(2);
return p.toFixed(6);
return formatUpbitKrwPrice(p, '-');
}
function fmtSize(s: number): string {
+2 -1
View File
@@ -3,6 +3,7 @@ import React, { useState } from 'react';
import { useDraggablePanel } from '../hooks/useDraggablePanel';
import { placePaperOrder, placeLiveOrder, loadLiveSummary } from '../utils/backendApi';
import { formatSignalTime } from '../utils/tradeSignalDisplay';
import { formatUpbitKrwPrice } from '../utils/safeFormat';
import { TRADE_BUY_COLOR, TRADE_SELL_COLOR } from '../utils/tradeSignalColors';
import { useTradeAlertTimeFormat } from '../utils/tradeAlertTimeFormat';
@@ -30,7 +31,7 @@ interface Props {
}
const fmt = (n: number): string =>
n > 0 ? n.toLocaleString('ko-KR', { maximumFractionDigits: 0 }) : '0';
n > 0 ? formatUpbitKrwPrice(n, '0') : '0';
function execLabel(type?: string, candleType?: string): string {
+14 -6
View File
@@ -7,7 +7,7 @@ import { getKoreanName } from '../utils/marketNameCache';
import { MarketSearchPanel } from './MarketSearchPanel';
import type { TradeOrderFillRequest } from '../types';
import { placePaperOrder } from '../utils/backendApi';
import { coerceFiniteNumber } from '../utils/safeFormat';
import { coerceFiniteNumber, formatUpbitKrwPrice, upbitKrwTickSize } from '../utils/safeFormat';
export type OrderKind = 'limit' | 'market' | 'stop_limit';
@@ -39,7 +39,7 @@ export interface TradeOrderPanelProps {
export function fmtKrw(n: number): string {
if (!Number.isFinite(n)) return '0';
return Math.round(n).toLocaleString('ko-KR');
return formatUpbitKrwPrice(n, '0');
}
function parseNum(s: string): number {
@@ -47,11 +47,19 @@ function parseNum(s: string): number {
return Number.isFinite(v) ? v : 0;
}
/** KRW 가격: 숫자만 허용, 천 단위 쉼표 표시 */
/** KRW 가격: 숫자·소수점(필요 시)만 허용, 업비트 규칙으로 표시 */
function sanitizePriceInput(raw: string): string {
const digits = raw.replace(/\D/g, '');
if (!digits) return '';
return Number(digits).toLocaleString('ko-KR');
const cleaned = raw.replace(/[^\d.]/g, '');
if (!cleaned) return '';
const n = Number(cleaned);
if (!Number.isFinite(n)) return '';
return formatUpbitKrwPrice(n, cleaned);
}
/** 현재 가격에 맞는 호가 단위만큼 증감 */
export function stepPriceByTick(price: number, direction: 1 | -1): number {
const tick = upbitKrwTickSize(price);
return Math.round((price + direction * tick) / tick) * tick;
}
/** 주문 수량: 숫자와 소수점(1개)만 허용 */
+2 -2
View File
@@ -2,6 +2,7 @@ import React, { useRef, useEffect, useLayoutEffect, useState, useCallback } from
import type { MouseEventParams, Time } from 'lightweight-charts';
import type { OHLCVBar, ChartType, Theme, IndicatorConfig, LegendData, Drawing, ChartMode, Timeframe } from '../types';
import { ChartManager } from '../utils/ChartManager';
import { formatUpbitKrwPrice } from '../utils/safeFormat';
import { useChartTimeFormat } from '../utils/chartTimeFormat';
import { setIndicatorChartContext } from '../utils/indicatorRegistry';
import { DISPLAY_COUNT } from '../hooks/useUpbitData';
@@ -1675,8 +1676,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
container.style.cursor = isHit ? 'pointer' : '';
}, []);
const fmtCtxPrice = (p: number) =>
p >= 1000 ? Math.round(p).toLocaleString('ko-KR') : p.toFixed(p >= 1 ? 2 : 6);
const fmtCtxPrice = (p: number) => formatUpbitKrwPrice(p);
const closeCtxAndRequest = useCallback((side: 'buy' | 'sell') => {
if (!ctxMenu || !market) return;
@@ -19,17 +19,18 @@ import {
drawdownSeries,
} from '../../utils/analysisReportMetrics';
import { repairUtf8Mojibake } from '../../utils/textEncoding';
import { formatUpbitKrwPrice } from '../../utils/safeFormat';
/* ── 포맷 유틸 ── */
const pct = (v: number, dec = 2) => `${v >= 0 ? '+' : ''}${(v * 100).toFixed(dec)}%`;
const pctAbs= (v: number, dec = 1) => `${(v * 100).toFixed(dec)}%`;
const num = (v: number, dec = 2) => (isFinite(v) ? v.toFixed(dec) : '—');
const wonFmt= (v: number) => {
const wonFmt = (v: number) => {
const abs = Math.abs(v);
const sign = v < 0 ? '-' : v > 0 ? '+' : '';
if (abs >= 1e8) return `${sign}${(abs / 1e8).toFixed(1)}`;
if (abs >= 1e4) return `${sign}${(abs / 1e4).toFixed(0)}`;
return `${sign}${abs.toLocaleString('ko-KR')}`;
return `${sign}${formatUpbitKrwPrice(abs, '0')}`;
};
const colorCls = (v: number) => (v > 0 ? 'up' : v < 0 ? 'down' : '');
@@ -617,8 +618,8 @@ function LogsView({ ctx }: { ctx: AnalysisReportContext }) {
<td>{r.dateTime}</td>
<td className="qcc-sym">{r.symbol.replace('KRW-', 'KRW-')}</td>
<td className="qcc-side">{r.side}</td>
<td className="qcc-num">{r.entryPrice.toLocaleString('ko-KR')}</td>
<td className="qcc-num">{r.exitPrice.toLocaleString('ko-KR')}</td>
<td className="qcc-num">{formatUpbitKrwPrice(r.entryPrice)}</td>
<td className="qcc-num">{formatUpbitKrwPrice(r.exitPrice)}</td>
<td className="qcc-num">{r.qty.toFixed(3)}</td>
<td className={r.pnlPct >= 0 ? 'up' : 'down'}>{r.pnlPct >= 0 ? '+' : ''}{r.pnlPct.toFixed(1)}%</td>
<td className="qcc-dur">{r.durationSec > 0 ? fmtDuration(r.durationSec) : '—'}</td>
@@ -658,7 +659,7 @@ function AllocationView({ ctx }: { ctx: AnalysisReportContext }) {
<span className="qcc-alloc-dot" style={{ background: sl.color }} />
<span className="qcc-alloc-sym">{sl.symbol.replace('KRW-', 'KRW-')}</span>
<span className="qcc-alloc-pct">{(sl.pct * 100).toFixed(2)}%</span>
<span className="qcc-alloc-krw">{Math.round(sl.capitalKrw).toLocaleString('ko-KR')}</span>
<span className="qcc-alloc-krw">{formatUpbitKrwPrice(sl.capitalKrw)}</span>
</div>
))}
</div>
@@ -3,6 +3,7 @@ import type { BacktestAnalysis } from '../../utils/backendApi';
import BacktestSparkline from './BacktestSparkline';
import type { EquityPoint } from '../../utils/backtestEquity';
import { krw, pct, pctAbs } from '../../utils/backtestUiUtils';
import { formatUpbitKrwPrice } from '../../utils/safeFormat';
interface Props {
analysis: BacktestAnalysis | null;
@@ -21,7 +22,7 @@ interface Props {
const num = (v: number, dec = 2) => (isFinite(v) ? v.toFixed(dec) : '');
const fmtWon = (v: number) =>
isFinite(v) ? `${Math.round(v).toLocaleString('ko-KR')}` : '';
isFinite(v) ? `${formatUpbitKrwPrice(v, '')}` : '';
function MetricCell({
label,
@@ -1,6 +1,6 @@
import React, { memo } from 'react';
import { useUpbitOrderbook } from '../../hooks/useUpbitOrderbook';
import { coerceFiniteNumber, safeToFixed } from '../../utils/safeFormat';
import { coerceFiniteNumber, safeToFixed, formatUpbitKrwPrice } from '../../utils/safeFormat';
interface Props {
market: string;
@@ -14,8 +14,7 @@ interface Props {
function fmtPrice(p: unknown): string {
const n = coerceFiniteNumber(p);
if (n == null) return '-';
return n >= 1000 ? n.toLocaleString('ko-KR', { maximumFractionDigits: 0 }) : safeToFixed(n, 4);
return formatUpbitKrwPrice(n, '-');
}
function fmtSize(s: unknown): string {
@@ -1,5 +1,6 @@
import React, { useMemo } from 'react';
import type { TrendSearchResultDto } from '../../utils/trendSearchApi';
import { formatUpbitKrwPrice } from '../../utils/safeFormat';
interface Props {
results: TrendSearchResultDto[];
@@ -10,9 +11,7 @@ interface Props {
}
function fmtPrice(n: number): string {
if (n >= 1_000_000) return n.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
if (n >= 100) return n.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
return n.toLocaleString('ko-KR', { maximumFractionDigits: 4 });
return formatUpbitKrwPrice(n, '-');
}
function fmtPct(n: number): string {
@@ -1,15 +1,10 @@
import React, { memo, useEffect, useRef, useState } from 'react';
import type { ChangeDir, TickerData } from '../../hooks/useMarketTicker';
import { coerceFiniteNumber, safePercentFromRate, safeToFixed } from '../../utils/safeFormat';
import { coerceFiniteNumber, safePercentFromRate, formatUpbitKrwPrice } from '../../utils/safeFormat';
function fmtKrw(price: number | null | undefined | unknown): string {
const p = coerceFiniteNumber(price);
if (p == null) return '-';
if (p >= 1_000_000) return p.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
if (p >= 1_000) return p.toLocaleString('ko-KR', { maximumFractionDigits: 0 });
if (p >= 1) return p.toLocaleString('ko-KR', { maximumFractionDigits: 2 });
if (p >= 0.01) return safeToFixed(p, 4, '-');
return safeToFixed(p, 8, '-');
return formatUpbitKrwPrice(p, '-');
}
function fmtPct(rate: number | null | undefined | unknown): string {