Skip to content

HTTP Polling Stream ​

market-feed/stream provides a market-hours-aware async generator that polls quotes at configurable intervals. It pauses completely when the exchange is closed, emits session boundary events, and detects price divergence across providers.

Basic usage ​

ts
import { watch } from "market-feed/stream";
import { MarketFeed } from "market-feed";

const feed = new MarketFeed();
const controller = new AbortController();

for await (const event of watch(feed, ["AAPL", "MSFT"], {
  signal: controller.signal,
})) {
  if (event.type === "quote") {
    console.log(`${event.symbol}: $${event.quote.price}`);
  }
}

controller.abort(); // stop the stream

Event types ​

All events share a timestamp: Date field.

quote ​

ts
{
  type: "quote";
  symbol: string;
  quote: Quote;
  timestamp: Date;
}

Emitted once per symbol per poll cycle.

market-open ​

ts
{
  type: "market-open";
  exchange: ExchangeId;
  session: "pre" | "regular";
  timestamp: Date;
}

Emitted when the stream detects a session transition from closed → open.

market-close ​

ts
{
  type: "market-close";
  exchange: ExchangeId;
  session: "post" | "closed";
  timestamp: Date;
}

divergence ​

ts
{
  type: "divergence";
  symbol: string;
  quotes: Quote[];        // one per responding provider
  spreadPct: number;      // (max - min) / mean × 100
  timestamp: Date;
}

Emitted when multiple providers return prices that differ by more than divergenceThreshold percent.

error ​

ts
{
  type: "error";
  error: Error;
  symbol?: string;
  recoverable: boolean;  // false = generator is about to terminate
  timestamp: Date;
}

earnings_released ​

ts
{
  type: "earnings_released";
  symbol: string;
  earnings: EarningsEvent;  // the newly detected quarterly report
  timestamp: Date;
}

Emitted when the stream detects a new quarterly earnings report for a watched symbol. Only emitted when includeFundamentals: true. Requires a provider with earnings() support (Yahoo Finance, Polygon).

ts
for await (const event of watch(feed, ["AAPL", "MSFT"], {
  includeFundamentals: true,
})) {
  if (event.type === "earnings_released") {
    console.log(
      `${event.symbol} new earnings: EPS ${event.earnings.epsActual} ` +
      `(est. ${event.earnings.epsEstimate})`
    );
  }
}

Options ​

ts
interface WatchOptions {
  /** Exchange calendar for session detection. Default: "NYSE" */
  exchange?: ExchangeId;

  interval?: {
    /** Poll interval during regular hours (ms). Default: 5 000 */
    open?: number;
    /** Poll interval during pre/post market (ms). Default: 30 000 */
    prepost?: number;
    /** Check interval when market is closed (ms). Default: 60 000 */
    closed?: number;
  };

  /** Pause polling during closed sessions. Default: true */
  marketHoursAware?: boolean;

  /** % price spread that triggers a divergence event. Default: 0.5 */
  divergenceThreshold?: number;

  /** Consecutive errors before the generator throws. Default: 5 */
  maxErrors?: number;

  /** AbortSignal to stop the stream. */
  signal?: AbortSignal;

  /**
   * When true, monitors watched symbols for new quarterly earnings reports
   * and emits `earnings_released` events when a newer period is detected.
   * Default: false
   */
  includeFundamentals?: boolean;

  /**
   * How often to check for new earnings data, in milliseconds.
   * Only relevant when `includeFundamentals: true`.
   * Default: 900_000 (15 minutes)
   */
  fundamentalsIntervalMs?: number;
}

Market-hours awareness ​

When marketHoursAware: true (default), the generator:

  1. Checks the exchange session before each poll
  2. During "closed" sessions, sleeps for interval.closed ms instead of fetching
  3. Emits market-open / market-close events when the session changes

This eliminates wasted API calls overnight and on weekends:

00:00 — closed, checking every 60s
...
09:30 — market-open event emitted
09:30 — quote events every 5s
16:00 — market-close event emitted
16:00 — post-market events every 30s
20:00 — closed, checking every 60s

Multi-provider divergence ​

When the MarketFeed has multiple providers, the stream fetches quotes from all of them each cycle. If prices diverge beyond divergenceThreshold, a divergence event is emitted alongside the quote events:

ts
const feed = new MarketFeed({
  providers: [
    new YahooProvider(),
    new PolygonProvider({ apiKey: process.env.POLYGON_KEY! }),
  ],
});

for await (const event of watch(feed, ["AAPL"], { divergenceThreshold: 1.0 })) {
  if (event.type === "divergence") {
    console.log(`Provider spread: ${event.spreadPct.toFixed(2)}%`);
  }
}

Stopping the stream ​

Always pass an AbortSignal so the stream can be cleanly cancelled:

ts
const controller = new AbortController();

// Stop after 30 seconds
setTimeout(() => controller.abort(), 30_000);

for await (const event of watch(feed, ["AAPL"], { signal: controller.signal })) {
  // ...
}
// generator returns cleanly after abort

vs. market-feed/ws ​

market-feed/streammarket-feed/ws
TransportHTTP pollingWebSocket
LatencySeconds (configurable interval)Sub-second (tick-by-tick)
ProvidersAll (Yahoo, AV, Polygon, etc.)Polygon + Finnhub only
Data granularityQuote snapshotsIndividual trade executions
Market-hours awareYes (built-in)No
Session eventsYesNo

Released under the MIT License.