Skip to content

Scan & Poll Orchestration

The scan and poll modules form the runtime pipeline of tradingview-strategy. scan.js performs a single full read of all indicators, while poll.js runs scans on a repeating interval with change detection.


scan.js -- Full Pipeline

scan() is the top-level pipeline orchestrator. It performs four sequential steps in a single invocation:

Step 1: Parallel MCP Reads

Six data sources are read simultaneously via Promise.all:

Reader Source
readChartState() Chart metadata (symbol, timeframe)
readPrice() Current price data
readBias() HTF Bias AI Pine graphics (tables, lines, labels)
readSession() Session Bias Pine graphics (lines, labels, boxes)
readOrderblocks() OB AI Pine graphics (lines, boxes, labels, tables)
readSMT() SMT AI Pine graphics (lines, labels)

Each reader calls MCP tools (tv_get_pine_lines, tv_get_pine_labels, etc.) with the appropriate study_filter from contracts/indicator-names.js.

Step 2: Interpret Each Reader's Output

The raw MCP data passes through five interpreters that convert visual elements into typed signals:

Interpreter Input Output
interpretBias(rawBias) Labels, lines, tables Bias direction, DOL target, PDH/PDL/PWH/PWL levels
interpretSession(rawSession) Labels, lines, boxes Hunt side, sweep status, re-extension state, session mode
interpretOrderblocks(rawOB) Lines, boxes, labels, tables Bullish/bearish OB arrays, ATR/risk data
interpretSMT(rawSMT) Lines, labels SMT signals with direction, paired instrument
interpretPrice(rawPrice) Price data Current price, bid/ask if available

Step 3: Session Context

The current ET time is computed and used to determine:

  • current_window -- via getCurrentWindow(hour, minute, mode) from session-times.js
  • in_macro -- via checkMacro(hour, minute) from macro-windows.js
  • entry_window_active -- whether current time falls within the ENTRY_WINDOW for the detected session mode

Step 4: Build Flags

All interpreted signals and session context are passed to buildFlags(), which assembles the final StrategyFlags JSON object. This includes running the confluence scorer, no-trade filter, and setup detector.

Standalone Execution

node src/scan.js

When run directly, scan() executes once and prints the resulting StrategyFlags as formatted JSON to stdout. Exits with code 1 on failure.


poll.js -- Interval-Based Scanning

poll() wraps scan() in a repeating loop with configurable interval and delta detection.

Options

Parameter Type Default Description
interval number 10000 Polling interval in milliseconds
onFlags function -- Called with every scan result
onDelta function -- Called only when flags change from previous scan
onError function -- Called when a scan throws an error
signal AbortSignal -- AbortSignal to stop the polling loop

Delta Detection

The computeDelta() function compares two consecutive StrategyFlags snapshots and tracks changes across 9 key fields:

Tracked field Change description
bias.daily Daily bias direction changed
bias.hit_target DOL target was hit (only triggers on false -> true)
session.hunt.side Hunt side changed (e.g., null -> HUNT_LOW)
session.current_window Session window transition (e.g., gap_scan -> ny_am)
decision.setup_valid Setup validity toggled
decision.confluence_score Confluence score changed
structure.order_blocks.bullish.length Bullish OB count changed
structure.order_blocks.bearish.length Bearish OB count changed
smt.last_signal SMT signal changed

When any tracked field changes, onDelta receives an object with { changed: true, changes: [...] } where changes is an array of human-readable strings like "bias_daily: bullish -> bearish".

AbortSignal Support

The poll loop checks signal.aborted before each iteration and listens for the abort event during the inter-scan wait. This allows clean shutdown:

const ac = new AbortController();
process.on('SIGINT', () => ac.abort());

await poll({ signal: ac.signal, interval: 10000 });

JSONL Output (Standalone)

When run directly via node src/poll.js, each scan prints a single status line:

[2026-04-14T14:30:05.123Z] SI1! 5 | VALID long (score: 7)
  CHANGES: hunt: null -> HUNT_LOW, window: gap_scan -> ny_am

The default interval is 10 seconds. Press Ctrl+C to stop (SIGINT triggers the AbortController).


Dashboard Integration

The dashboard (dashboard/server.js) does not use poll() directly. Instead, it calls scan() on demand:

  • The readCurrentChart endpoint in the dashboard server imports and calls scan() when the user requests a refresh.
  • The dashboard SSE (server-sent events) stream can trigger scans on a timer and push results to the browser.
  • All scan results are the same StrategyFlags JSON shape, whether from scan.js, poll.js, or the dashboard.