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-- viagetCurrentWindow(hour, minute, mode)from session-times.jsin_macro-- viacheckMacro(hour, minute)from macro-windows.jsentry_window_active-- whether current time falls within theENTRY_WINDOWfor 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¶
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
readCurrentChartendpoint in the dashboard server imports and callsscan()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
StrategyFlagsJSON shape, whether fromscan.js,poll.js, or the dashboard.