Market Events
Market events are first-class semantic timeline data: earnings, dividends, splits, filings, halts, futures lifecycle dates, economic releases, news, and host-defined event families.
This is a native SDK surface, not a copied marks layer. A provider must declare an exact catalog before it can emit events, and custom event types are registered from explicit sourceId + typeId ownership. The chart does not infer event meaning from marker labels, payload shape, titles, or provider names.
Event lifecycle
Every event source — the price feed or a registered provider — moves through the same four phases:
- Declare. Support exists only when both
getEventsand a non-emptyeventCatalogexist. Declared-only support is reported as unavailable. - Refresh.
getEventsreceives the active symbol and interval, exact start/end timestamps, exact type and provider ids, an optional cursor, and a page limit. - Live.
subscribeEventsreceives the same scope plus a stable subscription id. Realtime callbacks publish only exact{ kind: 'upsert', event }or{ kind: 'remove', sourceId, eventId }operations. - Cleanup. The returned
Unsubscribestops the stream. Provider teardown removes only that provider's subscription and provider-owned events.
Identity and deduplication
Runtime identity is source-scoped: sourceId + id.
- An upsert with an existing
sourceId + idreplaces that event; a new pair inserts. - Provider subscriptions can upsert or remove events without affecting another provider that uses the same event id.
- Events at the same exact chart time form one count marker on the timeline — that is display clustering, not deduplication; every event keeps its identity.
- No event is deduplicated against a legacy mark, and no mark is converted into an event.
Minimal subscription, one emitted state, one cleanup path
The smallest live wiring on the feed side — a stream that upserts, retracts, and cleans up:
subscribeEvents(subscription, callback) {
const socket = connectEventStream(subscription); // your transport
// One emitted state: insert-or-replace by sourceId + id.
socket.onEvent((event) => callback({ kind: 'upsert', event }));
// Retraction: remove by identity.
socket.onRetract((eventId) =>
callback({ kind: 'remove', sourceId: 'primary-feed', eventId }));
// One cleanup path: the chart calls this on symbol change or teardown.
return () => socket.close();
}
Checkpoint: with a catalog declared and getEvents returning one earnings item, the marker renders on the timeline; a live upsert with the same sourceId + id moves/updates that marker instead of adding a second one; unsubscribing stops further updates.
Task: serve events from the price feed
Feeds advertise event support through onReady() only when both the method and catalog exist:
import { BUILT_IN_MARKET_EVENT_CATALOG } from '@tradescript/pro/sdk'
const datafeed = {
onReady() {
return {
supportsEvents: true,
eventProviderId: 'primary-feed',
eventCatalog: {
types: [
BUILT_IN_MARKET_EVENT_CATALOG.types.find((entry) => entry.id === 'earnings')!,
],
},
}
},
async getEvents(request) {
return { items: await loadEarnings(request) }
},
subscribeEvents(subscription, callback) {
return socket.subscribe(subscription, callback)
},
}
MarketDataController.getCapabilities() reports supportsEvents: true only when getEvents is implemented and eventCatalog.types is non-empty. Declared-only support is reported as unavailable.
Task: manage events from the controller
Use chart.marketEvents() or widget.marketEvents(chartId) for native event management:
const events = chart.marketEvents()
await events.refresh({ startTime, endTime })
events.get({ typeIds: ['earnings'], sourceIds: ['primary-feed'] })
Provider refresh failures are isolated per provider. A failing alternative-data source emits market-event-provider-error but does not discard healthy provider results.
Task: add providers beyond the price feed
Providers that do not belong to the primary price feed can be supplied at construction time or registered after mount:
const mounted = sdk.chart.mount({
// ...bars feed, symbol, mount...
marketEventProviders: [truthSocialProvider, optionsFlowProvider],
features: {
marketEvents: {
markers: true,
details: 'built-in',
historyDays: 30,
upcomingDays: 90,
},
},
})
const widget = await mounted.ready()
const events = widget.marketEvents()
const unregister = events.registerProvider(weatherProvider)
unregister()
unregister() is the provider-level cleanup path: it removes that provider's subscription and provider-owned events, leaving other sources untouched.
Task: define custom event types
Host-defined event types use exact ownership:
import { defineCustomMarketEventType } from '@tradescript/pro/sdk'
const descriptor = defineCustomMarketEventType({
sourceId: 'government-desk',
typeId: 'policy-announcement',
label: 'Policy Announcement',
group: {
id: 'custom:government-desk:announcements',
label: 'Announcements',
sourceId: 'government-desk',
},
marker: { glyph: '📣', color: '#f97316' },
fields: [{ key: 'speaker', label: 'Speaker', format: 'text' }],
})
const unsubscribeType = events.registerType(descriptor)
events.add({
id: 'white-house-1',
sourceId: 'government-desk',
type: descriptor.id,
symbols: [{ ticker: 'SPY', type: 'stock' }],
time: Date.now(),
timePrecision: 'exact',
title: 'Policy announcement',
data: { speaker: 'Treasury' },
})
Custom events are rejected unless their type descriptor is registered first. Custom field payloads must match the descriptor field keys and stay scalar, and event payloads are bounded to keep timeline state safe for layouts, storage, and realtime updates.
Marker icons, glyphs, and SVG paths
Every built-in type has a polished SDK-owned vector icon. Custom descriptors may reuse a built-in icon, supply any Unicode glyph, or provide bounded svgPath data in a normalized 14×14 view box:
marker: {
glyph: '🚀',
color: '#8b5cf6',
backgroundColor: '#0b1220',
}
// Or safe path data only — never raw <svg> markup.
marker: {
svgPath: 'M 2 7 L 6 11 L 12 3',
svgPathStyle: 'stroke',
color: '#22c55e',
}
Chart users can override a type's glyph and color persistently in Chart Settings → Events. Applications can also set ChartFeatures.marketEvents.markerStyles at construction time. SVG path data is length- and character-bounded; provider HTML and SVG elements are rejected.
Custom groups remain in their provider-owned namespace unless the descriptor explicitly uses a built-in group. The chart uses only catalog declarations; it never derives a type from a social post, URL, title, provider name, metadata object, or payload shape. Application-only metadata is returned to event listeners but is not displayed.
Task: push host events and control persistence
add, update, remove, set, and clear manage session-owned host events. Host CRUD cannot mutate provider-owned events even when an id collides. Host events are deliberately absent from saved layouts; register their descriptor and rehydrate them after widget creation, or expose them through a provider.
Only ChartDisplaySettings.marketEvents persists with chart display preferences. It stores the master visibility flag, exact per-type and per-provider overrides, importance filters, and density. Group and parent checkbox states are derived from visible children and are never stored as a second source of truth.
Display-only changes redraw from retained event data. Hiding a source/type, changing importance or density, and editing marker appearance do not refetch providers. Enabling a source or type outside the current fetched coverage requests the expanded provider scope once.
Markers, details, and settings
Events inside loaded chart history render through the dedicated tsMarketEvent overlay. Events at the same exact chart time form one count marker; clicking it opens an importance-ordered list. Built-in and custom provider events can share the same cluster.
There is no separate Events toolbar button. Master visibility, provider/group/type tri-state controls, importance, density, and marker customization live in the existing chart settings modal. Set details: 'host' to emit market-event-click without opening SDK details. openCenter and closeCenter remain headless controller state for hosts that deliberately compose their own event-center UI.
Security boundary
- Titles, summaries, attribution, and custom values render as plain text.
- Only
http:andhttps:links are interactive. - A custom descriptor may declare at most 32 rendered fields, and each event payload is capped at 64 KiB.
- Custom values must be declared scalars: text, number, boolean, currency, percent, date, or URL.
- Provider HTML, scripts, templates, callbacks, and host-only metadata are never executed or rendered automatically.
Build a deterministic provider fixture
For examples and browser tests, implement the public MarketEventProvider
contract in your test code. This keeps the same provider shape in development
and production while using only customer-supported contracts:
import {
BUILT_IN_MARKET_EVENT_CATALOG,
type MarketEventProvider,
} from '@tradescript/pro/sdk';
const earningsType = BUILT_IN_MARKET_EVENT_CATALOG.types.find(
(entry) => entry.id === 'earnings',
)!;
const fixtureProvider: MarketEventProvider = {
id: 'fixture-events',
catalog: { types: [earningsType] },
async getEvents(request) {
return {
items: [{
id: 'earnings-1',
sourceId: 'fixture-events',
type: 'earnings',
symbols: [request.symbol],
time: request.startTime + 60_000,
timePrecision: 'exact',
title: 'Fixture earnings release',
importance: 'high',
}],
};
},
};
Add a host-owned callback registry to subscribeEvents when the test must prove
live upsert, removal, and unsubscribe behavior. Register a second provider whose
getEvents rejects when you need to prove that one provider failure does not
discard healthy results.
News and legacy marks
The standalone News panel stays the full article stream. A curated news event is a separate semantic record and links to an article only through explicit relatedNewsIds or url values.
getMarks, getTimescaleMarks, their flags, refresh/clear methods, and click events are unchanged. No mark is automatically converted into an event, and no event is deduplicated against a mark. Implement domain events through an exact catalog plus getEvents; do not relabel generic marks.
The built-in taxonomy follows the distinctions commonly available from corporate-action and exchange feeds, including mergers, spin-offs, reorganizations, listings, and symbol/name changes. See Alpaca corporate actions and Nasdaq Daily List for the source-system distinction between generic marker transport and domain event records.
Chart events
| Event | Payload |
|---|---|
market-events-change | Current event list and reason (refresh, subscription, host, clear) |
market-event-provider-error | Provider id, recoverability, and cause |
market-event-catalog-change | Current catalog and optional source id |
Next steps
- Order Flow — microstructure data alongside semantic events.
- Datafeeds overview — where events fit among the other optional capabilities.