Agent-Market-Arena / src /lib /marketCalendar.js
Jimin Huang
add: Feature
5fa7a59
raw
history blame
5.76 kB
// Trading calendar utilities for NYSE trading days
// This implementation relies on the 'nyse-holidays' package for holiday detection.
let nyseModule = null
let nyseLoadAttempted = false
const holidayCache = new Map() // key: YYYY-MM-DD, value: boolean
async function loadNyseModule() {
if (nyseLoadAttempted) return nyseModule
nyseLoadAttempted = true
try {
// dynamic import so the app still runs even if the package isn't installed
nyseModule = await import('nyse-holidays')
} catch (_) {
nyseModule = null
}
return nyseModule
}
function toIsoDateString(dateLike) {
if (typeof dateLike === 'string') return dateLike.slice(0, 10)
try { return new Date(dateLike).toISOString().slice(0, 10) } catch { return '' }
}
function isWeekend(dateLike) {
try {
const d = new Date(dateLike)
const day = d.getUTCDay()
return day === 0 || day === 6
} catch {
return false
}
}
export async function isNyseHoliday(dateLike) {
const iso = toIsoDateString(dateLike)
if (!iso) return false
if (holidayCache.has(iso)) return holidayCache.get(iso)
await loadNyseModule()
let isHoliday = false
try {
if (nyseModule) {
const mod = nyseModule
const fn = (mod && typeof mod.isHoliday === 'function')
? mod.isHoliday
: (mod && mod.default && typeof mod.default.isHoliday === 'function')
? mod.default.isHoliday
: null
if (fn) {
// Pass mid-day UTC to avoid timezone shifting to previous/next day
isHoliday = !!fn(new Date(`${iso}T12:00:00Z`))
}
}
} catch (_) {
isHoliday = false
}
holidayCache.set(iso, isHoliday)
return isHoliday
}
export async function isNyseTradingDay(dateLike) {
if (isWeekend(dateLike)) return false
return !(await isNyseHoliday(dateLike))
}
export async function filterRowsToNyseTradingDays(rows) {
const list = Array.isArray(rows) ? rows : []
const out = []
for (const r of list) {
if (!r || !r.date) continue
if (await isNyseTradingDay(r.date)) out.push(r)
}
return out
}
export async function countMissingNyseTradingDaysBetween(startIso, endIso, presentDateSet) {
if (!startIso || !endIso) return 0
const present = new Set(Array.from(presentDateSet || []).map(toIsoDateString))
let missing = 0
try {
const start = new Date(startIso)
const end = new Date(endIso)
for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
const iso = d.toISOString().slice(0, 10)
if (await isNyseTradingDay(iso)) {
if (!present.has(iso)) missing++
}
}
} catch {
return 0
}
return missing
}
export async function isTradingDayForAsset(asset, dateLike) {
// Crypto trades 24/7, all calendar days are trading days
if (asset === 'BTC' || asset === 'ETH') return true
// Default to NYSE for stocks
return await isNyseTradingDay(dateLike)
}
export async function countMissingTradingDaysBetweenForAsset(asset, startIso, endIso, presentDateSet) {
if (!startIso || !endIso) return 0
const present = new Set(Array.from(presentDateSet || []).map(toIsoDateString))
let missing = 0
try {
const start = new Date(startIso)
const end = new Date(endIso)
for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
const iso = d.toISOString().slice(0, 10)
if (await isTradingDayForAsset(asset, iso)) {
if (!present.has(iso)) missing++
}
}
} catch {
return 0
}
return missing
}
export async function listMissingTradingDaysBetweenForAsset(asset, startIso, endIso, presentDateSet) {
if (!startIso || !endIso) return []
const present = new Set(Array.from(presentDateSet || []).map(toIsoDateString))
const missing = []
try {
const start = new Date(startIso)
const end = new Date(endIso)
for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
const iso = d.toISOString().slice(0, 10)
if (await isTradingDayForAsset(asset, iso)) {
if (!present.has(iso)) missing.push(iso)
}
}
} catch {
return []
}
return missing
}
export async function countNonTradingDaysBetweenForAsset(asset, startIso, endIso) {
if (!startIso || !endIso) return 0
// Crypto has no closed days by definition here
if (asset === 'BTC' || asset === 'ETH') return 0
let closed = 0
try {
const start = new Date(startIso)
const end = new Date(endIso)
for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
const iso = d.toISOString().slice(0, 10)
if (!(await isNyseTradingDay(iso))) closed++
}
} catch {
return 0
}
return closed
}
export async function listNonTradingDaysBetweenForAsset(asset, startIso, endIso) {
if (!startIso || !endIso) return []
if (asset === 'BTC' || asset === 'ETH') return []
const closed = []
try {
const start = new Date(startIso)
const end = new Date(endIso)
for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
const iso = d.toISOString().slice(0, 10)
if (!(await isNyseTradingDay(iso))) closed.push(iso)
}
} catch {
return []
}
return closed
}
export async function countTradingDaysBetweenForAsset(asset, startIso, endIso) {
if (!startIso || !endIso) return 0
try {
const start = new Date(startIso)
const end = new Date(endIso)
if (asset === 'BTC' || asset === 'ETH') {
const days = Math.max(0, Math.floor((end - start) / 86400000) + 1)
return days
}
let count = 0
for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) {
const iso = d.toISOString().slice(0, 10)
if (await isNyseTradingDay(iso)) count++
}
return count
} catch {
return 0
}
}