Session Schedules
A session schedule declares when a symbol trades — regular hours, pre-market, post-market, overnight futures sessions, holidays, and half-days. The SDK compiles the declaration into concrete epoch-ms windows that power session shading, break lines, session-aware aggregation, empty-bar generation, extended-hours filtering, and the session info badge.
Schedules are declarative data: wall times in an IANA timezone with explicit day offsets, states, and per-date corrections. DST is handled by the compiler, so a feed declares local exchange hours once instead of pre-computing UTC epochs.
How a trading day resolves
For any trading day, exactly one rule wins. Precedence, highest first:
holidayscontains the date → the day has no windows (closed).specialDayshas the date → exactly those windows are used (an empty list also closes the day).- Otherwise, the weekly rules in force: the latest
revisionsentry whoseeffectiveFromis on or before the date, else the baseweek.
A date may appear in holidays or specialDays, not both; the compiler rejects the ambiguity.
Compile and request lifecycle
- The spec is compiled once, when you create a provider with
createSessionCalendarProvider(spec)(invalid specs throw at that point, not at request time). - Each
resolveSessionCalendar(request)call then computes concrete windows for the requestedstartTime/endTimerange — deterministically, from the compiled spec. There is no background refresh or hidden cache to invalidate. - The chart and
createCachingDatafeedcallresolveSessionCalendaron demand: for session shading of the visible range, before building synthetic bars for a source/target interval pair, and for empty-bar generation. - To change trading rules over time, change the data — add a
revisionsentry or aspecialDayscorrection — and every later request reflects it.
Declaring a schedule
import type { SessionScheduleSpec } from '@tradescript/pro/sdk';
const usEquities: SessionScheduleSpec = {
timezone: 'America/New_York',
week: [
{
days: [1, 2, 3, 4, 5], // Monday through Friday
windows: [
{ open: '04:00', close: '09:30', state: 'pre-market' },
{ open: '09:30', close: '16:00' }, // state defaults to 'regular'
{ open: '16:00', close: '20:00', state: 'post-market' },
],
},
],
};
Each window is a wall-clock range on a trading day:
export interface SessionScheduleWindow {
open: string; // 'HH:MM' (00:00–24:00) in the schedule timezone
close: string; // 'HH:MM' (00:00–24:00)
openDayOffset?: number; // days relative to the trading day; negative opens before it
closeDayOffset?: number;
state?: SessionState; // built-in state or feed-owned custom id; defaults to 'regular'
}
state is the subsession identifier. Everything downstream — background shading colors, the session filter, session-aware symbol resolution, and the session badge — keys off it. Built-in states cover common market phases (pre-market, regular, post-market, closed, holiday, halted), and feeds can use custom ids such as opening-auction, cash-morning, night, or venue-specific auction phases. A single trading day carries as many windows as it has subsessions; closed periods can be omitted or represented with the built-in non-trading states when the UI should show them explicitly.
Overnight sessions
Day offsets are explicit; there is no start-greater-than-end inference. A CME globex-style session that opens the evening before its trading day:
const usFutures: SessionScheduleSpec = {
timezone: 'America/New_York',
week: [
{
days: [1, 2, 3, 4, 5],
windows: [{ open: '18:00', openDayOffset: -1, close: '17:00' }],
},
],
};
Verify cross-midnight and holiday behavior directly. buildSessionCalendar compiles a spec for a range without any chart, so precedence is testable in isolation:
import { buildSessionCalendar } from '@tradescript/pro/sdk';
const calendar = buildSessionCalendar(
{ ...usFutures, holidays: ['2024-01-15'] },
{
startTime: Date.parse('2024-01-11T00:00:00Z'),
endTime: Date.parse('2024-01-17T00:00:00Z'),
},
);
// The Friday 2024-01-12 window opens Thursday evening local time:
// opensAt = 2024-01-11T23:00:00Z (18:00 NY on Jan 11, openDayOffset -1)
// closesAt = 2024-01-12T22:00:00Z (17:00 NY on Jan 12)
// The 2024-01-15 holiday contributes no window at all.
Cross-midnight windows belong to their tradingDay, so a daily bar for that trading day and the overnight session that precedes it aggregate together.
Presets
Four ready-made schedules ship with the SDK:
import {
US_EQUITIES_SESSION_SCHEDULE, // pre 04:00–09:30, regular 09:30–16:00, post 16:00–20:00 NY time
US_FUTURES_SESSION_SCHEDULE, // 18:00 prior day – 17:00 NY time, Mon–Fri trading days
CONTINUOUS_SESSION_SCHEDULE, // 24x7 single regular window
WEEKDAY_CONTINUOUS_SESSION_SCHEDULE, // Sunday 17:00 open – Friday 17:00 close NY convention
} from '@tradescript/pro/sdk';
A preset is an explicitly selected fact: declare it on the symbols it genuinely describes. The SDK never applies a preset because of an instrument's asset classification — a crypto-classified symbol gets CONTINUOUS_SESSION_SCHEDULE only when your feed says so.
Exchange holidays are venue data, not part of the presets — merge them in via holidays and specialDays when your feed knows them.
Holidays and half-days
holidays closes a trading day entirely. specialDays replaces that day's windows per session state in one place:
const spec: SessionScheduleSpec = {
timezone: 'America/New_York',
week: [/* normal rules */],
holidays: ['2024-11-28'], // Thanksgiving: no sessions
specialDays: {
'2024-11-29': [ // half-day: early close, shortened post-market
{ open: '04:00', close: '09:30', state: 'pre-market' },
{ open: '09:30', close: '13:00' },
{ open: '13:00', close: '17:00', state: 'post-market' },
],
},
};
Schedule revisions
When exchange rules change over time, add a revision instead of forking the spec. Each revision applies exactly from its effective trading day:
const spec: SessionScheduleSpec = {
timezone: 'America/New_York',
week: [/* rules before the change */],
revisions: [
{ effectiveFrom: '2025-03-01', week: [/* rules from March 2025 on */] },
],
};
Wiring schedules into a feed
Schedules are per-symbol facts. Return each symbol's schedule from resolveSymbol and wrap the feed with withSymbolSessionSchedules — the wrapper synthesizes resolveSessionCalendar, resolveSessionInfo, and subscribeSessionInfo from whichever schedule the resolved symbol carries, so a mixed catalog serves equities, futures, and continuous venues from one feed:
import {
withSymbolSessionSchedules,
CONTINUOUS_SESSION_SCHEDULE,
US_EQUITIES_SESSION_SCHEDULE,
US_FUTURES_SESSION_SCHEDULE,
} from '@tradescript/pro/sdk';
const feed = withSymbolSessionSchedules({
async resolveSymbol(selection) {
const listing = await catalog.resolve(selection);
return {
...listing,
// The schedule is a declared fact of this listing, never a guess from
// its asset classification.
sessionSchedule: listing.venue === 'cme'
? US_FUTURES_SESSION_SCHEDULE
: listing.venue === 'binance'
? CONTINUOUS_SESSION_SCHEDULE
: US_EQUITIES_SESSION_SCHEDULE,
};
},
loadBars,
subscribeRealTimeBars,
});
Symbols without a sessionSchedule fall through to the wrapped feed's own session methods when it has them. A single-venue feed can instead implement resolveSessionCalendar directly with createSessionCalendarProvider(spec) — an explicit statement that every symbol it serves trades on that one venue calendar:
import { createSessionCalendarProvider, US_EQUITIES_SESSION_SCHEDULE } from '@tradescript/pro/sdk';
const singleVenueFeed: MarketDataFeed = {
loadBars,
subscribeRealTimeBars,
resolveSessionCalendar: createSessionCalendarProvider(US_EQUITIES_SESSION_SCHEDULE),
};
With a calendar in place the chart shades session areas on intraday resolutions, draws session break lines, aligns synthetic aggregation buckets to session opens, and can generate empty bars inside active sessions. See Resolutions for the aggregation rules and Session Filtering for hiding extended hours.
For a one-off range (tests, precomputation), buildSessionCalendar(spec, { startTime, endTime, symbol? }) returns the compiled SessionAggregationCalendar directly.
Session info providers
The same spec answers "what session is this symbol in right now" — the data behind resolveSessionInfo / subscribeSessionInfo and the session badge widget:
import { createSessionInfoProvider, sessionInfoAt } from '@tradescript/pro/sdk';
const feed: MarketDataFeed = {
// ...
resolveSessionInfo: createSessionInfoProvider(US_EQUITIES_SESSION_SCHEDULE),
};
// Or compute a snapshot directly:
const info = sessionInfoAt(US_EQUITIES_SESSION_SCHEDULE, symbol, Date.now());
// info.currentState → 'pre-market', info.upcoming → next windows within the horizon
createSessionInfoProvider accepts { horizonMs, now } — how far ahead upcoming windows are collected (default 7 days) and an injectable clock for tests.
Next steps
- Session Filtering — use the calendar to hide extended hours or custom phases.
- Resolutions — how the calendar drives synthetic bar buckets.