export interface KisRealtimeStockTick { symbol: string; // 종목코드 time: string; // 체결시간 currentPrice: number; // 현재가 sign: string; // 전일대비부호 (1:상한, 2:상승, 3:보합, 4:하한, 5:하락) change: number; // 전일대비 changeRate: number; // 전일대비율 accumulatedVolume: number; // 누적거래량 } const STOCK_realtime_TR_ID = "H0STCNT0"; // H0STCNT0 Output format indices based on typical KIS Realtime API // Format: MKSC_SHRN_ISCD^STCK_CNTG_HOUR^STCK_PRPR^PRDY_VRSS_SIGN^PRDY_VRSS^PRDY_CTRT^... const STOCK_FIELD_INDEX = { symbol: 0, // MKSC_SHRN_ISCD time: 1, // STCK_CNTG_HOUR currentPrice: 2, // STCK_PRPR sign: 3, // PRDY_VRSS_SIGN change: 4, // PRDY_VRSS changeRate: 5, // PRDY_CTRT accumulatedVolume: 12, // ACML_VOL (Usually at index 12 or similar, need to be careful here) } as const; export function parseKisRealtimeStockTick( raw: string, ): KisRealtimeStockTick | null { // Format: 0|H0STCNT0|001|SYMBOL^TIME^PRICE^SIGN^CHANGE^... if (!/^([01])\|/.test(raw)) return null; const parts = raw.split("|"); if (parts.length < 4) return null; // Check TR ID if (parts[1] !== STOCK_realtime_TR_ID) { return null; } const values = parts[3].split("^"); if (values.length < 6) return null; // Ensure minimum fields exist const symbol = values[STOCK_FIELD_INDEX.symbol]; const currentPrice = parseFloat(values[STOCK_FIELD_INDEX.currentPrice]); const sign = values[STOCK_FIELD_INDEX.sign]; const changeRaw = parseFloat(values[STOCK_FIELD_INDEX.change]); const changeRateRaw = parseFloat(values[STOCK_FIELD_INDEX.changeRate]); // Adjust sign for negative values if necessary const isNegative = sign === "5" || sign === "4"; // 5: 하락, 4: 하한 const change = isNegative ? -Math.abs(changeRaw) : Math.abs(changeRaw); const changeRate = isNegative ? -Math.abs(changeRateRaw) : Math.abs(changeRateRaw); // Validate numeric values if (isNaN(currentPrice)) return null; return { symbol, time: values[STOCK_FIELD_INDEX.time], currentPrice, sign, change, changeRate, accumulatedVolume: parseFloat(values[STOCK_FIELD_INDEX.accumulatedVolume]) || 0, }; }