270 lines
8.3 KiB
TypeScript
270 lines
8.3 KiB
TypeScript
/**
|
|
* @file lib/kis/dashboard-helpers.ts
|
|
* @description 대시보드 계산/포맷 공통 헬퍼 모음
|
|
*/
|
|
|
|
/**
|
|
* @description 기준일 기준 N일 범위의 YYYYMMDD 기간을 반환합니다.
|
|
* @see lib/kis/dashboard.ts getDomesticOrderHistory/getDomesticTradeJournal 조회 기간 계산
|
|
*/
|
|
export function getLookbackRangeYmd(lookbackDays: number) {
|
|
const end = new Date();
|
|
const start = new Date(end);
|
|
start.setDate(end.getDate() - lookbackDays);
|
|
|
|
return {
|
|
startDate: formatYmd(start),
|
|
endDate: formatYmd(end),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @description Date를 YYYYMMDD 문자열로 변환합니다.
|
|
* @see lib/kis/dashboard-helpers.ts getLookbackRangeYmd
|
|
*/
|
|
export function formatYmd(date: Date) {
|
|
const year = String(date.getFullYear());
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
return `${year}${month}${day}`;
|
|
}
|
|
|
|
/**
|
|
* @description 문자열에서 숫자만 추출합니다.
|
|
* @see lib/kis/dashboard.ts 주문일자/매매일자/시각 정규화
|
|
*/
|
|
export function toDigits(value?: string) {
|
|
return (value ?? "").replace(/\D/g, "");
|
|
}
|
|
|
|
/**
|
|
* @description 주문 시각을 HHMMSS로 정규화합니다.
|
|
* @see lib/kis/dashboard.ts getDomesticOrderHistory 정렬/표시 시각 생성
|
|
*/
|
|
export function normalizeTimeDigits(value?: string) {
|
|
const digits = toDigits(value);
|
|
if (!digits) return "000000";
|
|
return digits.padEnd(6, "0").slice(0, 6);
|
|
}
|
|
|
|
/**
|
|
* @description YYYYMMDD를 YYYY-MM-DD로 변환합니다.
|
|
* @see lib/kis/dashboard.ts 주문내역/매매일지 날짜 컬럼 표시
|
|
*/
|
|
export function formatDateLabel(value: string) {
|
|
if (value.length !== 8) return "-";
|
|
return `${value.slice(0, 4)}-${value.slice(4, 6)}-${value.slice(6, 8)}`;
|
|
}
|
|
|
|
/**
|
|
* @description HHMMSS를 HH:MM:SS로 변환합니다.
|
|
* @see lib/kis/dashboard.ts 주문내역 시각 컬럼 표시
|
|
*/
|
|
export function formatTimeLabel(value: string) {
|
|
if (value.length !== 6) return "-";
|
|
return `${value.slice(0, 2)}:${value.slice(2, 4)}:${value.slice(4, 6)}`;
|
|
}
|
|
|
|
/**
|
|
* @description KIS 매수/매도 코드를 공통 side 값으로 변환합니다.
|
|
* @see lib/kis/dashboard.ts getDomesticOrderHistory/getDomesticTradeJournal
|
|
*/
|
|
export function parseTradeSide(
|
|
code?: string,
|
|
name?: string,
|
|
): "buy" | "sell" | "unknown" {
|
|
const normalizedCode = (code ?? "").trim();
|
|
const normalizedName = (name ?? "").trim();
|
|
|
|
if (normalizedCode === "01") return "sell";
|
|
if (normalizedCode === "02") return "buy";
|
|
if (normalizedName.includes("매도")) return "sell";
|
|
if (normalizedName.includes("매수")) return "buy";
|
|
return "unknown";
|
|
}
|
|
|
|
/**
|
|
* @description 매매일지 요약 기본값을 반환합니다.
|
|
* @see lib/kis/dashboard.ts getDomesticDashboardActivity 매매일지 실패 폴백
|
|
*/
|
|
export function createEmptyJournalSummary() {
|
|
return {
|
|
totalRealizedProfit: 0,
|
|
totalRealizedRate: 0,
|
|
totalBuyAmount: 0,
|
|
totalSellAmount: 0,
|
|
totalFee: 0,
|
|
totalTax: 0,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @description 문자열 숫자를 number로 변환합니다.
|
|
* @see lib/kis/dashboard.ts 잔고/지수 필드 공통 파싱
|
|
*/
|
|
export function toNumber(value?: string) {
|
|
if (!value) return 0;
|
|
const normalized = value.replaceAll(",", "").trim();
|
|
if (!normalized) return 0;
|
|
const parsed = Number(normalized);
|
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
}
|
|
|
|
/**
|
|
* @description 문자열 숫자를 number로 변환하되 0도 유효값으로 유지합니다.
|
|
* @see lib/kis/dashboard.ts 요약값 폴백 순서 계산
|
|
*/
|
|
export function toOptionalNumber(value?: string) {
|
|
if (!value) return undefined;
|
|
const normalized = value.replaceAll(",", "").trim();
|
|
if (!normalized) return undefined;
|
|
const parsed = Number(normalized);
|
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
}
|
|
|
|
/**
|
|
* @description output 계열 데이터를 배열 형태로 변환합니다.
|
|
* @see lib/kis/dashboard.ts 잔고 output1/output2 파싱
|
|
*/
|
|
export function parseRows<T>(value: unknown): T[] {
|
|
if (Array.isArray(value)) return value as T[];
|
|
if (value && typeof value === "object") return [value as T];
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* @description output 계열 데이터의 첫 행을 반환합니다.
|
|
* @see lib/kis/dashboard.ts 잔고 요약(output2) 파싱
|
|
*/
|
|
export function parseFirstRow<T>(value: unknown) {
|
|
const rows = parseRows<T>(value);
|
|
return rows[0];
|
|
}
|
|
|
|
/**
|
|
* @description 지수 output을 단일 레코드로 정규화합니다.
|
|
* @see lib/kis/dashboard.ts getDomesticDashboardIndices
|
|
*/
|
|
export function parseIndexRow<T extends object>(
|
|
output: unknown,
|
|
): T {
|
|
if (Array.isArray(output) && output[0] && typeof output[0] === "object") {
|
|
return output[0] as T;
|
|
}
|
|
if (output && typeof output === "object") {
|
|
return output as T;
|
|
}
|
|
return {} as T;
|
|
}
|
|
|
|
/**
|
|
* @description KIS 부호 코드(1/2 상승, 4/5 하락)를 실제 부호로 반영합니다.
|
|
* @see lib/kis/dashboard.ts getDomesticDashboardIndices
|
|
*/
|
|
export function normalizeSignedValue(value: number, signCode?: string) {
|
|
const abs = Math.abs(value);
|
|
if (signCode === "4" || signCode === "5") return -abs;
|
|
if (signCode === "1" || signCode === "2") return abs;
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* @description undefined가 아닌 첫 값을 반환합니다.
|
|
* @see lib/kis/dashboard.ts 요약 수익률/손익 폴백 계산
|
|
*/
|
|
export function firstDefinedNumber(...values: Array<number | undefined>) {
|
|
return values.find((value) => value !== undefined) ?? 0;
|
|
}
|
|
|
|
/**
|
|
* @description 숫자 배열 합계를 계산합니다.
|
|
* @see lib/kis/dashboard.ts 보유종목 합계 계산
|
|
*/
|
|
export function sumNumbers(values: number[]) {
|
|
return values.reduce((total, value) => total + value, 0);
|
|
}
|
|
|
|
/**
|
|
* @description 총자산 대비 손익률을 계산합니다.
|
|
* @see lib/kis/dashboard.ts 요약 수익률 폴백 계산
|
|
*/
|
|
export function calcProfitRate(profit: number, totalAmount: number) {
|
|
if (totalAmount <= 0) return 0;
|
|
const baseAmount = totalAmount - profit;
|
|
if (baseAmount <= 0) return 0;
|
|
return (profit / baseAmount) * 100;
|
|
}
|
|
|
|
/**
|
|
* @description 매입금액 대비 손익률을 계산합니다.
|
|
* @see lib/kis/dashboard.ts getDomesticDashboardBalance 현재 손익률 산출
|
|
*/
|
|
export function calcProfitRateByPurchase(profit: number, purchaseAmount: number) {
|
|
if (purchaseAmount <= 0) return 0;
|
|
return (profit / purchaseAmount) * 100;
|
|
}
|
|
|
|
/**
|
|
* @description 총자산과 평가금액 기준으로 일관된 현금성 자산(예수금)을 계산합니다.
|
|
* @remarks UI 흐름: 대시보드 API 응답 생성 -> 총자산/평가금/예수금 카드 반영
|
|
* @see lib/kis/dashboard.ts getDomesticDashboardBalance
|
|
*/
|
|
export function resolveCashBalance(params: {
|
|
apiReportedTotalAmount: number;
|
|
apiReportedNetAssetAmount: number;
|
|
evaluationAmount: number;
|
|
cashCandidates: Array<number | undefined>;
|
|
}) {
|
|
const {
|
|
apiReportedTotalAmount,
|
|
apiReportedNetAssetAmount,
|
|
evaluationAmount,
|
|
cashCandidates,
|
|
} = params;
|
|
const referenceTotalAmount = pickPreferredAmount(
|
|
apiReportedNetAssetAmount,
|
|
apiReportedTotalAmount,
|
|
);
|
|
const candidateCash = pickPreferredAmount(...cashCandidates);
|
|
const derivedCash =
|
|
referenceTotalAmount > 0
|
|
? Math.max(referenceTotalAmount - evaluationAmount, 0)
|
|
: undefined;
|
|
|
|
if (derivedCash === undefined) return candidateCash;
|
|
|
|
const recomposedWithCandidate = candidateCash + evaluationAmount;
|
|
const mismatchWithApi = Math.abs(
|
|
recomposedWithCandidate - referenceTotalAmount,
|
|
);
|
|
if (mismatchWithApi >= 1) {
|
|
return derivedCash;
|
|
}
|
|
|
|
return candidateCash;
|
|
}
|
|
|
|
/**
|
|
* @description 금액 후보 중 양수 값을 우선 선택합니다.
|
|
* @see lib/kis/dashboard.ts getDomesticDashboardBalance 금액 필드 폴백 계산
|
|
*/
|
|
export function pickPreferredAmount(...values: Array<number | undefined>) {
|
|
const positive = values.find(
|
|
(value): value is number => value !== undefined && value > 0,
|
|
);
|
|
if (positive !== undefined) return positive;
|
|
return firstDefinedNumber(...values);
|
|
}
|
|
|
|
/**
|
|
* @description 숫자 후보 중 0이 아닌 값을 우선 선택합니다.
|
|
* @see lib/kis/dashboard.ts getDomesticDashboardBalance 손익/수익률 폴백 계산
|
|
*/
|
|
export function pickNonZeroNumber(...values: Array<number | undefined>) {
|
|
const nonZero = values.find(
|
|
(value): value is number => value !== undefined && value !== 0,
|
|
);
|
|
if (nonZero !== undefined) return nonZero;
|
|
return firstDefinedNumber(...values);
|
|
}
|