대시보드 실시간 기능 추가

This commit is contained in:
2026-02-13 12:17:35 +09:00
parent 12feeb2775
commit 1ac907cd27
35 changed files with 2790 additions and 1032 deletions

View File

@@ -0,0 +1,62 @@
export interface KisRealtimeIndexTick {
symbol: string; // 업종코드 (0001: KOSPI, 1001: KOSDAQ)
price: number; // 현재가
change: number; // 전일대비
changeRate: number; // 전일대비율
sign: string; // 대비부호
time: string; // 체결시간
}
const INDEX_REALTIME_TR_ID = "H0UPCNT0";
const INDEX_FIELD_INDEX = {
symbol: 0, // bstp_cls_code
time: 1, // bsop_hour
price: 2, // prpr_nmix
sign: 3, // prdy_vrss_sign
change: 4, // bstp_nmix_prdy_vrss
accumulatedVolume: 5, // acml_vol
accumulatedAmount: 6, // acml_tr_pbmn
changeRate: 9, // prdy_ctrt
} as const;
export function parseKisRealtimeIndexTick(
raw: string,
): KisRealtimeIndexTick | null {
// Format: 0|H0UPCNT0|001|0001^123456^...
if (!/^([01])\|/.test(raw)) return null;
const parts = raw.split("|");
if (parts.length < 4) return null;
// Check TR ID
if (parts[1] !== INDEX_REALTIME_TR_ID) {
return null;
}
const values = parts[3].split("^");
if (values.length < 10) return null; // Ensure minimum fields exist
const symbol = values[INDEX_FIELD_INDEX.symbol];
const price = parseFloat(values[INDEX_FIELD_INDEX.price]);
const sign = values[INDEX_FIELD_INDEX.sign];
const changeRaw = parseFloat(values[INDEX_FIELD_INDEX.change]);
const changeRateRaw = parseFloat(values[INDEX_FIELD_INDEX.changeRate]);
// Adjust sign for negative values if necessary (usually API sends absolute values for change)
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);
return {
symbol,
time: values[INDEX_FIELD_INDEX.time],
price,
change,
changeRate,
sign,
};
}

View File

@@ -0,0 +1,69 @@
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,
};
}