spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1import { describe, expect, it } from "vitest";2import type { Exchange } from "@market-atlas/market-model";3import { MarketCalendar } from "./calendar.js";45const xnys: Exchange = { id: "xnys", mic: "XNYS", name: "NYSE", operator: null, country: "US", city: null, timezone: "America/New_York", currency: "USD", website: null, lat: null, lon: null, assetClasses: ["EQUITY"], sessions: { regular: [{ open: "09:30", close: "16:00" }], pre: { open: "04:00", close: "09:30" }, post: { open: "16:00", close: "20:00" } } };6const xtks: Exchange = { ...xnys, id: "xtks", timezone: "Asia/Tokyo", sessions: { regular: [{ open: "09:00", close: "11:30" }, { open: "12:30", close: "15:30" }] } };7const xsau: Exchange = { ...xnys, id: "xsau", timezone: "Asia/Riyadh", sessions: { regular: [{ open: "10:00", close: "15:00" }], weekdays: [0, 1, 2, 3, 4] } };8const coinbase: Exchange = { ...xnys, id: "coinbase", timezone: "UTC", sessions: { regular: [], continuous: true } };910const cal = new MarketCalendar();11cal.load([xnys, xtks, xsau, coinbase], [12 { exchangeId: "xnys", date: "2026-09-07", name: "Labor Day", kind: "CLOSED", sourceId: "t" },13 { exchangeId: "xnys", date: "2026-11-27", name: "Early close", kind: "EARLY_CLOSE", closeTime: "13:00", sourceId: "t" },14]);1516const at = (iso: string) => Date.parse(iso);1718describe("market calendar", () => {19 it("NYSE regular, pre, post, closed and weekend", () => {20 expect(cal.status("xnys", at("2026-09-11T14:00:00Z")).state).toBe("OPEN"); // 10:00 EDT Friday21 expect(cal.status("xnys", at("2026-09-11T12:00:00Z")).state).toBe("PRE"); // 08:00 EDT22 expect(cal.status("xnys", at("2026-09-11T21:00:00Z")).state).toBe("POST"); // 17:00 EDT23 expect(cal.status("xnys", at("2026-09-12T14:00:00Z")).state).toBe("CLOSED"); // Saturday24 expect(cal.status("xnys", at("2026-09-11T02:00:00Z")).state).toBe("CLOSED"); // 22:00 EDT Thursday25 });26 it("holidays and early closes", () => {27 const labor = cal.status("xnys", at("2026-09-07T14:00:00Z"));28 expect(labor.state).toBe("CLOSED");29 expect(labor.isHoliday).toBe(true);30 expect(labor.holidayName).toBe("Labor Day");31 expect(cal.status("xnys", at("2026-11-27T17:00:00Z")).state).toBe("OPEN"); // 12:00 EST32 expect(cal.status("xnys", at("2026-11-27T18:30:00Z")).state).toBe("CLOSED"); // 13:30 EST after early close33 });34 it("DST: winter session in UTC shifts by one hour", () => {35 expect(cal.status("xnys", at("2026-01-15T14:00:00Z")).state).toBe("PRE"); // 09:00 EST36 expect(cal.status("xnys", at("2026-01-15T14:30:00Z")).state).toBe("OPEN");37 });38 it("Tokyo lunch break and Riyadh Sunday trading", () => {39 expect(cal.status("xtks", at("2026-09-11T01:00:00Z")).state).toBe("OPEN"); // 10:00 JST40 expect(cal.status("xtks", at("2026-09-11T03:00:00Z")).state).toBe("CLOSED"); // 12:00 JST lunch41 expect(cal.status("xtks", at("2026-09-11T04:00:00Z")).state).toBe("OPEN"); // 13:00 JST42 expect(cal.status("xsau", at("2026-09-13T08:00:00Z")).state).toBe("OPEN"); // Sunday 11:00 AST43 expect(cal.status("xsau", at("2026-09-11T08:00:00Z")).state).toBe("CLOSED"); // Friday44 });45 it("continuous venues are always open; next transition computed for NYSE", () => {46 expect(cal.status("coinbase", at("2026-09-12T03:00:00Z")).state).toBe("OPEN");47 const s = cal.status("xnys", at("2026-09-11T14:00:00Z"));48 expect(s.nextTransition?.state).toBe("POST");49 expect(new Date(s.nextTransition!.at).toISOString()).toBe("2026-09-11T20:00:00.000Z");50 });51 it("session start for the trading day", () => {52 expect(new Date(cal.sessionStart("xnys", at("2026-09-11T14:00:00Z"))!).toISOString()).toBe("2026-09-11T08:00:00.000Z"); // pre-market 04:00 EDT53 });54});55