// OHLCV → CSV 변환 + 다운로드 트리거. // 차트 데이터를 분석가 친화 CSV (UTF-8 BOM, Excel 호환) 로 내려받게 해줌. import type { ChartInterval, OhlcvPoint } from "./api"; function escapeField(v: string): string { // 표준 RFC 4180 — 쉼표/큰따옴표/개행 포함 시 큰따옴표로 감싸고 내부 큰따옴표는 이중화. if (v.includes(",") || v.includes('"') || v.includes("\n")) { return `"${v.replace(/"/g, '""')}"`; } return v; } function num(n: number | null | undefined): string { if (n == null || !Number.isFinite(n)) return ""; return String(n); } export function ohlcvToCsv(ohlcv: OhlcvPoint[]): string { const header = ["date", "open", "high", "low", "close", "volume"]; const lines: string[] = [header.join(",")]; for (const p of ohlcv) { lines.push( [ escapeField(p.date), num(p.open), num(p.high), num(p.low), num(p.close), num(p.volume), ].join(","), ); } return lines.join("\n"); } export function downloadCsv(filename: string, csv: string): boolean { if (typeof window === "undefined" || typeof document === "undefined") return false; // Excel BOM — 한글 Excel 이 UTF-8 로 추론하도록. const BOM = "\uFEFF"; const blob = new Blob([BOM + csv], { type: "text/csv;charset=utf-8" }); const url = URL.createObjectURL(blob); try { const a = document.createElement("a"); a.href = url; a.download = filename; a.style.display = "none"; document.body.appendChild(a); a.click(); document.body.removeChild(a); return true; } catch { return false; } finally { setTimeout(() => URL.revokeObjectURL(url), 0); } } // `삼성전자_005930_1d_2025-08-01_2025-11-28.csv` — 파일시스템 금지 문자만 `_` 치환. export function csvFilename( name: string, code: string, interval: ChartInterval, from: string, to: string, ): string { const safe = (s: string) => s.replace(/[\\/:*?"<>|]/g, "_").trim() || "data"; return `${safe(name)}_${code}_${interval}_${from}_${to}.csv`; }