/* ── 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 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()), }); /* ── TAB IDs ─────────────────────────────────────────────────────── */ export const TAB_PORTFOLIO = 'portfolio'; export const TAB_AI = 'ai'; export const TAB_REPORT = 'report'; export const TAB_ADVISOR = 'advisor';