Skip to main content

mcx_history.py — Trading Calendar & Holiday Builder

MCX publishes its holiday list for the current year only — no year selector, no archive. This module reconstructs the full 2003→today calendar from a different, undocumented endpoint, and exposes MCXWorkingDays() for every other module that needs to know which dates MCX actually traded.

from mcxeod.mcx_history import update_mcx_holidays, MCXWorkingDays

update_mcx_holidays() # full 2003 -> today rebuild
days = MCXWorkingDays(date(2020, 1, 1), date(2020, 12, 31))
cd backend
python -m mcxeod.mcx_history # rebuild trading calendar + holidays

Rebuild annually each January.


Two Data Sources

SourceEndpointCoversGives
Historical turnoverGetHistoricalDataDetails2003 → today, per commodityProof a date was open — no OHLC, only TradedContract / Quantity / TotalValue
Holiday pagetrading-surveillance/trading-holidaysCurrent year onlyHoliday names and per-session (morning_open / evening_open) status

The historical endpoint can prove MCX was open; it can never give prices — prices only ever come from the bhavcopy (mcx_downloader.py).


build_trading_calendar(start, end, client) → set[date]

Always take the union across anchors — never trust a single commodity. GOLDM didn't exist until 2003-11-20 and was idle on 2003-11-21 even though MCX was open that day; a GOLDM-only calendar would invent a holiday.

AnchorTrading fromNote
GOLD2003-11-18Day one — alone matched the full union (6,328 days)
SILVER2003-11-18Day one
GOLDM2003-11-20Idle 2003-11-21 while MCX was open — unsafe alone
CRUDEOILlaterModern redundancy

The API rejects windows ≥360 days, so _chunks() splits any range into 355-day pages.


update_mcx_holidays(start, end, calendar, apply=True) → dict

Rebuilds the calendar rows for exch = 2 (MCX) in the shared holidays / specialdays tables.

  • holidays (exch=2) — weekdays MCX did not trade
  • specialdays (exch=2) — weekends MCX did trade (Saturdays 2005–2012ish, Muhurat/budget Sundays)

Holiday name resolution order

  1. Current-year holiday_page() scrape, if the date falls in this year
  2. _nse_holiday_names() — MCX full closures proved to be a subset of NSE holidays (holidays table, exch=1); 96 of 101 historical names resolved this way
  3. "MCX Holiday (derived)" — used for the remaining 5 (all 2004–2006), each individually verified against that day's bhavcopy as a genuine closure. Never guess a name — leave the derived label and log it.

Session flags

morning_open / evening_open come from the current-year holiday page only. They are NULL for history — an EOD turnover feed cannot say which session traded; NULL honestly means "traded, sessions unknown". Only morning_open = false AND evening_open = false counts as a fully blocked working day.

Deletes and inserts are always scoped WHERE exch = 2 — an MCX rebuild must never touch the NSE rows (exch = 1) that supply the fallback names.

nseeod/utils.py's updateholidays() deletes are exch-scoped too

If that scoping is ever removed from the NSE side, the next NSE refresh destroys the MCX calendar built here.


MCXWorkingDays(fromdate, todate) → list[date]

The calendar-aware replacement for a weekday() < 5 filter, used by mcx_backfill.py and anywhere else that needs a historically-correct MCX trading range.

day in weekend_sessions OR (day.weekday() < 5 AND day not in holidays)
Never filter MCX dates by weekday

MCX ran 490 Saturday sessions (2004-07-03 → 2025-02-01) and 5 Sunday sessions (Muhurat/budget days: 2013-11-03, 2016-10-30, 2019-10-27, 2023-11-12, 2026-02-01). A plain weekday() < 5 walk silently discards all 495 of them. McxProcessor.run_range_download() still does this — it's scoped to the "sync recent dates" UI path only; do not reuse it for historical work.

MCXHolidays() → list[str]

MCX non-trading dates as 'YYYY-MM-DD' strings, mirroring nseeod.utils.NseHolidays().


Calendar Facts (built 2026-08-17, local tedb)

Trading days 2003-11-18 → 2026-08-146,328
Holidays (holidays, exch=2)101 (~4.4/yr — full closures only)
…of which unnamed (derived)5 (all 2004–2006, each bhavcopy-verified)
Weekend sessions (specialdays, exch=2)495 (490 Sat + 5 Sun)

  • mcx_downloader.pyMcxProcessor.run_range_download() uses a plain weekday filter, not MCXWorkingDays()
  • mcx_backfill.py — the primary consumer of MCXWorkingDays()