Files
auto-trade/lib/kis/dashboard-helpers.ts

270 lines
8.3 KiB
TypeScript
Raw Normal View History

2026-02-26 09:05:17 +09:00
/**
* @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);
}