177 lines
4.9 KiB
TypeScript
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 };
|
||
|
|
}
|