/* ── helpers ─────────────────────────────────────────────────────── */ export const formatNumber = (value) => { if (value === null || value === undefined || value === '') return '-'; const numeric = Number(value); if (Number.isNaN(numeric)) return value; return new Intl.NumberFormat('ko-KR').format(numeric); }; export const formatPercent = (value) => { if (value === null || value === undefined || value === '') return '-'; if (typeof value === 'string' && value.includes('%')) return value; const numeric = Number(value); if (Number.isNaN(numeric)) return value; return `${numeric >= 0 ? '+' : ''}${numeric.toFixed(2)}%`; }; export const pickFirst = (...values) => values.find((value) => value !== undefined && value !== null && value !== ''); export const getQty = (item) => pickFirst(item?.qty, item?.quantity, item?.holding, item?.hold_qty); export const getBuyPrice = (item) => pickFirst( item?.buy_price, item?.avg_price, item?.avg, item?.purchase_price, item?.buyPrice, item?.price ); export const getCurrentPrice = (item) => pickFirst( item?.current_price, item?.current, item?.cur_price, item?.now_price, item?.market_price ); export const getProfitRate = (item) => pickFirst( item?.profit_rate, item?.profitRate, item?.profit_pct, item?.profitPercent, item?.pnl_rate, item?.return_rate, item?.yield ); export const getProfitLoss = (item) => pickFirst(item?.profit_loss, item?.pnl, item?.profitLoss); export const toNumeric = (value) => { if (value === null || value === undefined || value === '') return null; const numeric = Number(String(value).replace(/[^0-9.-]/g, '')); return Number.isNaN(numeric) ? null : numeric; }; /* ── Chart colors ──────────────────────────────────────────────── */ export const CHART_COLORS = ['#818cf8', '#fbbf24', '#34d399', '#f472b6', '#fb923c', '#a78bfa', '#38bdf8', '#4ade80']; export const profitColorClass = (numericValue) => { if (numericValue > 0) return 'is-up'; if (numericValue < 0) return 'is-down'; if (numericValue === 0) return 'is-flat'; return ''; }; export const numFitClass = (text) => { const len = String(text ?? '').length; if (len >= 13) return 'is-fit-xs'; if (len >= 10) return 'is-fit-sm'; return ''; }; export const getVixLabel = (vix) => { if (vix < 12) return '극히 낮음 (안일 주의)'; if (vix < 20) return '정상 (안정적)'; if (vix < 30) return '주의 (불확실성 증가)'; if (vix < 40) return '높음 (극도의 공포)'; return '극단 (패닉)'; }; export const getFgLabel = (score) => { if (score <= 25) return '극단적 공포'; if (score <= 45) return '공포'; if (score <= 55) return '중립'; if (score <= 75) return '탐욕'; return '극단적 탐욕'; }; /* ── empty portfolio form ────────────────────────────────────────── */ export const emptyPortfolioForm = { broker: '', ticker: '', name: '', quantity: '', avg_price: '', purchase_price: '', }; /* ── empty sell-history form ─────────────────────────────────────── */ export const toLocalDatetimeValue = (isoStr) => { if (!isoStr) return ''; const d = new Date(isoStr); const pad = (n) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; }; export const emptySellForm = () => ({ broker: '', ticker: '', name: '', quantity: '', avg_price: '', sell_price: '', commission: '', sold_at: toLocalDatetimeValue(new Date().toISOString()), }); /* ── 증권사별 요약 집계 ──────────────────────────────────────────── */ // totalBuy: 총 매입 = 각 종목 매입가(purchase_price)의 단순 합 (수량 미곱산, 박재오 정의). // 매입가 미설정 시 avg_price 폴백. 백엔드 total_buy(×수량)는 표시에 쓰지 않음. // totalCostBasis: 손익 계산용 매입원가 = SUM(avg_price × quantity) — 손익은 수량 곱산 유지. export const computeBrokerSummary = (items) => { let totalBuy = 0, totalCostBasis = 0, totalEval = 0, hasNullPrice = false; for (const item of items) { const qty = item.quantity ?? 0; const purchase = item.purchase_price ?? item.avg_price ?? 0; totalBuy += purchase; totalCostBasis += (item.avg_price ?? 0) * qty; if (item.eval_amount != null) totalEval += item.eval_amount; else hasNullPrice = true; } const totalProfit = totalEval - totalCostBasis; const totalProfitRate = totalCostBasis > 0 ? (totalProfit / totalCostBasis) * 100 : 0; return { totalBuy, totalEval, totalProfit, totalProfitRate, hasNullPrice }; }; /* ── TAB IDs ─────────────────────────────────────────────────────── */ export const TAB_PORTFOLIO = 'portfolio'; export const TAB_REPORT = 'report'; export const TAB_ADVISOR = 'advisor';