Skip to content

Real-time stock pattern scanner with Polygon.io websocket integration

Notifications You must be signed in to change notification settings

edkim/realtime-scanner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stocks Watch

Real-time stock pattern scanner that connects to Polygon.io/Massive.com websocket and alerts when patterns are detected. Features professional-grade filters, market context awareness, and a web-based control panel.

Features

  • Real-time data: Connects to Polygon/Massive websocket for live market data
  • Pattern scanning: Detects various technical patterns in real-time with professional-grade filters
  • Web UI: Full-featured control panel for managing tickers, scan frequency, and patterns
  • Market context: Tracks SPY/QQQ bias and daily trends for directional alignment
  • Universe filters: Liquidity, volatility, and in-play (RVOL) filtering
  • Risk management: Centralized 2:1 R:R framework with pattern-specific overrides
  • Analytics: Signal outcome tracking for performance evaluation
  • Historical backfill: Automatically backfills data when starting mid-session
  • Configurable alerts: Console alerts by default, extensible to Discord/Telegram/SMS

Quick Start

# Install dependencies
npm install

# Copy environment file and add your API key
cp .env.example .env
# Edit .env and add your POLYGON_API_KEY

# Run the scanner (starts both scanner and web UI)
npm run dev

Then open your browser to http://localhost:3000 to access the web control panel.

Patterns Included

1. Gap and Hold

Detects gap-up patterns that hold above previous day's close throughout the session.

  • Signal: Stock gaps up 2%+ and maintains above previous close
  • Filters: Requires in-play (RVOL), bullish/neutral market, avoids daily resistance
  • Best for: Morning strength, trend continuation
  • Bias: Long
  • R:R: 2:1

2. Opening Range Breakout

Detects price breaking above the 5-minute opening range (09:30-09:35 ET) with increased volume.

  • Signal: Price breaks above OR high with 1.2x+ opening range volume
  • Filters: Requires in-play, bullish/neutral market, daily uptrend preferred, rejection filter
  • Best for: Early momentum breakouts
  • Bias: Long
  • R:R: 2:1

3. Opening Range Breakdown

Detects price breaking below the 5-minute opening range (09:30-09:35 ET) with increased volume.

  • Signal: Price breaks below OR low with 1.2x+ opening range volume
  • Filters: Requires in-play, bearish/neutral market, daily downtrend preferred, rejection filter
  • Best for: Early weakness/short setups
  • Bias: Short
  • R:R: 2:1

4. New Session High

Detects when price breaks above session high, signaling potential breakout.

  • Signal: Price makes new high for current session (close-based, not wick-only)
  • Filters: Avoids late-session exhaustion, daily resistance, requires market alignment
  • Best for: Breakout momentum, trend continuation
  • Bias: Long
  • R:R: 2:1

5. New Session Low

Detects when price breaks below session low, signaling potential breakdown.

  • Signal: Price makes new low for current session (close-based, not wick-only)
  • Filters: Avoids late-session exhaustion, daily support, requires market alignment
  • Best for: Breakdown momentum, short continuation
  • Bias: Short
  • R:R: 2:1

6. VWAP Fade

Detects weak stocks that fade below VWAP and stay there throughout the session.

  • Signal: 60-80%+ of bars trade below VWAP with VWAP test-and-fail
  • Filters: Requires VWAP test-and-fail, avoids daily support, bearish/neutral market, avoids extreme moves
  • Severity levels: EMERGING (60-69%) → CONFIRMED (70-79%) → EXTREME (80%+)
  • Best for: Risk-off environments, persistent weakness
  • Bias: Short
  • R:R: 2:1

Web UI

The scanner includes a full-featured web control panel at http://localhost:3000:

  • Status Dashboard: Real-time scanner status, ticker counts, scan statistics, market bias
  • Ticker Management: Add/remove tickers dynamically without restarting
  • Scan Frequency Control: Adjust scan interval from 30 seconds to 5 minutes
  • Pattern Toggles: Enable/disable patterns in real-time
  • Active Signals: View all active signals with context badges (RVOL, market bias, daily trend)
  • Signal Details: Entry, stop, target, R:R ratio, and contextual metadata

Professional Features

Universe Filters

The scanner automatically filters tickers based on:

  • Price: Minimum $5 (configurable)
  • Liquidity: Minimum average daily dollar volume (default: $20M)
  • Volatility: Minimum ATR threshold (default: $0.10)
  • In-Play Detection: Relative volume (RVOL) filtering for high-intent patterns
    • Compares current session volume vs 20-day average
    • Default threshold: 1.5x RVOL

Market Context

The scanner tracks market context for better signal quality:

  • SPY/QQQ Bias: Monitors index direction (bullish/neutral/bearish)
  • Daily Trend: Tracks 20/50-day MA relationships
  • Daily Levels: Identifies proximity to daily resistance/support
  • Directional Alignment: Only takes long patterns in bullish/neutral markets, short patterns in bearish/neutral markets

Risk Management

All patterns use a centralized risk framework:

  • Consistent R:R: Default 2:1 risk/reward ratio
  • Structural Stops: Stops placed at logical invalidation levels
  • Pattern-Specific Overrides: Can customize R multiples per pattern
  • ATR-Based Utilities: Optional ATR-based stop calculation

Analytics

Signal outcomes are automatically tracked:

  • MFE/MAE: Max favorable/adverse excursion in R multiples
  • Stop/Target Hits: Tracks which signals hit stops vs targets
  • Context Tagging: Market bias, RVOL, time of day, daily trend
  • Performance Summaries: Win rate and average R by pattern
  • Persistent Storage: Outcomes saved to data/analytics/outcomes.json

Configuration

Environment Variables

# Required
POLYGON_API_KEY=your_key_here

# Optional - comma-separated list of tickers
SCAN_TICKERS=AAPL,MSFT,GOOGL,AMD

Config File (config.json)

The scanner uses config.json for persistent configuration:

{
  "tickers": ["AAPL", "MSFT", "GOOGL"],
  "scanIntervalMs": 300000,
  "patterns": {
    "Gap and Hold": { "enabled": true },
    "Opening Range Breakout": { "enabled": false },
    "New Session High": { "enabled": true }
  }
}

Configuration can be managed via the web UI or by editing config.json directly.

Default Watchlist

If SCAN_TICKERS is not set, the scanner watches:

  • Indices: QQQ, SPY, IWM (required for market context)
  • Mega-cap tech: AAPL, MSFT, NVDA, AMZN, GOOGL, META, TSLA
  • Tech/Semis: AMD, INTC, AVGO, NFLX, CRM
  • Crypto-adjacent: ETHE, GBTC, MSTR, MARA, CLSK, RIOT
  • Blue chips: BA, CAT, JPM, GS, V, MA
  • Volatility: UVXY, SOXL, TQQQ

Note: SPY and QQQ are automatically included for market context tracking.

Architecture

┌─────────────────┐
│  Polygon WS     │ → 1-min bars for tickers
└────────┬────────┘
         ↓
┌─────────────────┐
│  Market State   │ → In-memory sliding window (300 bars per ticker)
└────────┬────────┘
         ↓
┌─────────────────┐
│  Universe       │ → Filters (price, liquidity, RVOL, ATR)
│  Filters        │
└────────┬────────┘
         ↓
┌─────────────────┐
│  Market Context │ → SPY/QQQ bias, daily trends, key levels
└────────┬────────┘
         ↓
┌─────────────────────────────────┐
│  Pattern Scanners (concurrent)   │
│  - Gap and Hold                  │
│  - Opening Range Breakout        │
│  - Opening Range Breakdown       │
│  - New Session High              │
│  - New Session Low               │
│  - VWAP Fade                     │
└────────┬────────────────────────┘
         ↓
┌─────────────────┐
│  Risk Utils     │ → Centralized R:R calculation
└────────┬────────┘
         ↓
┌─────────────────┐
│  Analytics      │ → Outcome tracking & performance
└────────┬────────┘
         ↓
┌─────────────────┐
│  Alert System   │ → Console, Discord, etc.
└─────────────────┘
         ↓
┌─────────────────┐
│  Web UI         │ → Control panel & signal display
└─────────────────┘

Historical Data Backfill

When starting mid-session, the scanner automatically:

  1. Fetches previous day's close using Polygon daily aggregates (efficient single call)
  2. Backfills today's bars from 09:30 ET to current time using minute aggregates
  3. Populates market state before starting real-time scanning

This ensures patterns work correctly even when starting mid-day, as they have access to:

  • Previous day's close (for gap calculations)
  • Today's session open (09:30 ET)
  • Session high/low from 09:30
  • Complete RTH bars for accurate VWAP and metadata

⚠️ Important Notes

Regular Trading Hours (RTH) Only

  • All metadata (prevDayClose, todayOpen, VWAP) uses RTH bars only (09:30-16:00 ET)
  • Extended hours bars are received but filtered out of calculations

Timezone Handling

  • All timestamps normalized to Eastern Time (ET)
  • Works correctly regardless of server location
  • Handles EDT/EST transitions automatically

Startup Requirements

  • Best practice: Start before 09:30 ET to accumulate full session data
  • Patterns need 20-50 bars to function; wait ~30 minutes after startup mid-session
  • Historical backfill handles mid-session starts automatically

In-Memory State

  • All data stored in RAM (sliding window of 300 bars per ticker)
  • Crash = lose all state (no persistence)
  • Memory usage: ~50MB for 500 tickers
  • Analytics outcomes are persisted to disk

Market Context Requirements

  • SPY and QQQ must be in your watchlist for market bias tracking
  • Market context defaults to neutral if SPY/QQQ data unavailable
  • Daily trend calculations use simplified MA from intraday bars (for production, consider fetching daily aggregates)

Scripts

# Development with auto-reload (scanner + web UI)
npm run dev

# Build for production
npm run build

# Run production build
npm start

# Run without auto-reload
npm run watch

API Endpoints

The web UI communicates with the scanner via REST API:

  • GET /api/status - Scanner status and statistics
  • GET /api/tickers - List configured tickers
  • POST /api/tickers - Update tickers
  • GET /api/scan-frequency - Get scan interval
  • POST /api/scan-frequency - Update scan interval
  • GET /api/patterns - List all patterns with enabled state
  • POST /api/patterns/:name/enable - Enable a pattern
  • POST /api/patterns/:name/disable - Disable a pattern
  • GET /api/signals - Get active signals
  • GET /api/market-context - Get current market bias

Adding New Patterns

Create a new pattern file in src/patterns/:

// src/patterns/my-pattern.ts
import { Pattern, TickerState, Signal } from './types';
import { universeFilters } from '../filters/universe-filters';
import { marketContextService } from '../context/market-context';
import { calculateRisk, getPatternRMultiple } from '../risk/risk-utils';

export const MyPattern: Pattern = {
  name: 'My Pattern',
  description: 'Description of what it detects',
  minBars: 20,

  shouldScan(state: TickerState): boolean {
    // Pre-filter logic
    if (state.bars.length < 20) return false;
    
    // Apply universe filters
    if (!universeFilters.shouldScan(state, true)) return false; // require in-play
    
    // Check market alignment
    if (!marketContextService.isDirectionAligned('long')) return false;
    
    return true;
  },

  scan(state: TickerState): Signal | null {
    // Pattern detection logic
    
    // Use risk utils for consistent R:R
    const entry = current.close;
    const stop = someStructureLevel * 0.998;
    const rMultiple = getPatternRMultiple(this.name);
    const riskResult = calculateRisk({
      entry,
      stop,
      rMultiple
    }, 'long');
    
    if (riskResult.risk <= 0) return null;
    
    // Return signal with context metadata
    return {
      ticker: state.ticker,
      pattern: this.name,
      timestamp: current.timestamp,
      time: current.time,
      entry: riskResult.entry,
      stop: riskResult.stop,
      target: riskResult.target,
      confidence: calculatedConfidence,
      metadata: {
        rMultiple: riskResult.rMultiple,
        marketBias: marketContextService.getMarketContext()?.overallBias,
        rvol: universeFilters.getFilterStatus(state).rvol,
        isInPlay: universeFilters.getFilterStatus(state).isInPlay
      }
    };
  }
};

Then register it in src/index.ts:

import { MyPattern } from './patterns/my-pattern';

// In registerPatterns()
registry.register(MyPattern);

And add it to the default config in src/config/config-manager.ts:

patterns: {
  'My Pattern': { enabled: false }
}

Performance Considerations

  • Scan frequency: Default 5 minutes balances responsiveness vs CPU usage
  • Universe filters: Reduce false signals by filtering low-quality tickers
  • Market context: Prevents taking trades against market direction
  • In-play filtering: Focuses on high-probability setups with volume confirmation

License

MIT

About

Real-time stock pattern scanner with Polygon.io websocket integration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published