Files
auto-trade/lib/kis/domestic-market-session.ts
2026-02-11 11:18:15 +09:00

177 lines
4.9 KiB
TypeScript

/**
* @file lib/kis/domestic-market-session.ts
* @description KRX market-session helpers based on KST (Asia/Seoul)
*/
export type DomesticKisSession =
| "openAuction"
| "regular"
| "closeAuction"
| "afterCloseFixedPrice"
| "afterHoursSinglePrice"
| "closed";
export const DOMESTIC_KIS_SESSION_OVERRIDE_HEADER = "x-kis-session-override";
export const DOMESTIC_KIS_SESSION_OVERRIDE_STORAGE_KEY =
"KIS_SESSION_OVERRIDE";
const OPEN_AUCTION_START_MINUTES = 8 * 60 + 30; // 08:30
const OPEN_AUCTION_END_MINUTES = 9 * 60; // 09:00
const REGULAR_START_MINUTES = 9 * 60; // 09:00
const REGULAR_END_MINUTES = 15 * 60 + 20; // 15:20
const CLOSE_AUCTION_START_MINUTES = 15 * 60 + 20; // 15:20
const CLOSE_AUCTION_END_MINUTES = 15 * 60 + 30; // 15:30
const AFTER_CLOSE_FIXED_START_MINUTES = 15 * 60 + 40; // 15:40
const AFTER_CLOSE_FIXED_END_MINUTES = 16 * 60; // 16:00
const AFTER_HOURS_SINGLE_START_MINUTES = 16 * 60; // 16:00
const AFTER_HOURS_SINGLE_END_MINUTES = 18 * 60; // 18:00
/**
* @description Converts external string to strict session enum.
* @see lib/kis/domestic.ts getDomesticOrderBook
* @see features/dashboard/hooks/useKisTradeWebSocket.ts resolveSessionInClient
*/
export function parseDomesticKisSession(value?: string | null) {
if (!value) return null;
const normalized = value.trim();
if (!normalized) return null;
const allowed: DomesticKisSession[] = [
"openAuction",
"regular",
"closeAuction",
"afterCloseFixedPrice",
"afterHoursSinglePrice",
"closed",
];
return allowed.includes(normalized as DomesticKisSession)
? (normalized as DomesticKisSession)
: null;
}
/**
* @description Returns current session in KST.
* @see features/dashboard/hooks/useKisTradeWebSocket.ts WebSocket TR switching
* @see lib/kis/domestic.ts REST orderbook source switching
*/
export function getDomesticKisSessionInKst(now = new Date()): DomesticKisSession {
const { weekday, totalMinutes } = toKstWeekdayAndMinutes(now);
if (weekday === "Sat" || weekday === "Sun") {
return "closed";
}
if (
totalMinutes >= OPEN_AUCTION_START_MINUTES &&
totalMinutes < OPEN_AUCTION_END_MINUTES
) {
return "openAuction";
}
if (
totalMinutes >= REGULAR_START_MINUTES &&
totalMinutes < REGULAR_END_MINUTES
) {
return "regular";
}
if (
totalMinutes >= CLOSE_AUCTION_START_MINUTES &&
totalMinutes < CLOSE_AUCTION_END_MINUTES
) {
return "closeAuction";
}
if (
totalMinutes >= AFTER_CLOSE_FIXED_START_MINUTES &&
totalMinutes < AFTER_CLOSE_FIXED_END_MINUTES
) {
return "afterCloseFixedPrice";
}
if (
totalMinutes >= AFTER_HOURS_SINGLE_START_MINUTES &&
totalMinutes < AFTER_HOURS_SINGLE_END_MINUTES
) {
return "afterHoursSinglePrice";
}
return "closed";
}
/**
* @description If override is valid, use it. Otherwise use real KST time.
* @see app/api/kis/domestic/orderbook/route.ts session override header
* @see features/dashboard/hooks/useKisTradeWebSocket.ts localStorage override
*/
export function resolveDomesticKisSession(
override?: string | null,
now = new Date(),
) {
return parseDomesticKisSession(override) ?? getDomesticKisSessionInKst(now);
}
/**
* @description Maps detailed KIS session to dashboard phase.
* @see lib/kis/domestic.ts getDomesticOverview
*/
export function mapDomesticKisSessionToMarketPhase(
session: DomesticKisSession,
): "regular" | "afterHours" {
if (
session === "regular" ||
session === "openAuction" ||
session === "closeAuction"
) {
return "regular";
}
return "afterHours";
}
/**
* @description Whether orderbook should use overtime REST API.
* @see lib/kis/domestic.ts getDomesticOrderBook
*/
export function shouldUseOvertimeOrderBookApi(session: DomesticKisSession) {
return (
session === "afterCloseFixedPrice" || session === "afterHoursSinglePrice"
);
}
/**
* @description Whether trade tick should use expected-execution TR.
* @see features/dashboard/hooks/useKisTradeWebSocket.ts resolveTradeTrId
*/
export function shouldUseExpectedExecutionTr(session: DomesticKisSession) {
return session === "openAuction" || session === "closeAuction";
}
/**
* @description Whether trade tick/orderbook should use after-hours single-price TR.
* @see features/dashboard/hooks/useKisTradeWebSocket.ts resolveTradeTrId
*/
export function shouldUseAfterHoursSinglePriceTr(session: DomesticKisSession) {
return session === "afterHoursSinglePrice";
}
function toKstWeekdayAndMinutes(now: Date) {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: "Asia/Seoul",
weekday: "short",
hour: "2-digit",
minute: "2-digit",
hour12: false,
}).formatToParts(now);
const partMap = new Map(parts.map((part) => [part.type, part.value]));
const weekday = partMap.get("weekday") ?? "Sun";
const hour = Number(partMap.get("hour") ?? "0");
const minute = Number(partMap.get("minute") ?? "0");
const totalMinutes = hour * 60 + minute;
return { weekday, totalMinutes };
}