Architecture

The service is intentionally built around a Watcher / Action abstraction so that new observable events and new notification channels can be added without touching existing code.

High-Level Design

        flowchart TD
    subgraph startup["Startup"]
        CFG[Config\nconfig.yaml] --> SVC[WatcherService]
        MR[MarketResolver\nGamma REST API] --> SVC
    end

    SVC -->|asset_ids| WS[PolymarketWebSocketClient\nwss://ws-subscriptions-clob.polymarket.com/ws/market]

    subgraph event_loop["Event Loop"]
        WS -->|raw JSON frame| DISPATCH[_dispatch_event\nevent bus / fan-out]
        DISPATCH -->|book / price_change| PSW[PriceSupportWatcher]
        DISPATCH -.->|future events| FW1[SmartMoneyWatcher\nšŸ”® future]
        DISPATCH -.->|future events| FW2[MarketDisputeWatcher\nšŸ”® future]
        DISPATCH -.->|future events| FW3[ExternalPlatformWatcher\nšŸ”® future]
    end

    subgraph actions["Actions"]
        PSW -->|alert payload| LA[LogAction\nstdout placeholder]
        PSW -.->|alert payload| FA1[SMSAction\nšŸ”® future]
        PSW -.->|alert payload| FA2[DiscordAction\nšŸ”® future]
        PSW -.->|alert payload| FA3[TelegramAction\nšŸ”® future]
        FW1 -.->|alert payload| FA1
        FW1 -.->|alert payload| FA2
    end

    style startup fill:#f0f4ff,stroke:#aac
    style event_loop fill:#fff8e8,stroke:#ca9
    style actions fill:#f0fff4,stroke:#9c9
    

Module Map

polymarket_watcher/
ā”œā”€ā”€ __init__.py
ā”œā”€ā”€ config.py              ← dataclass-based YAML config loader
ā”œā”€ā”€ market_resolver.py     ← slug → (yes_token_id, no_token_id) via Gamma API
ā”œā”€ā”€ order_book.py          ← local OrderBook state, bid_support_within_pct()
ā”œā”€ā”€ websocket_client.py    ← auto-reconnecting WebSocket client (websockets lib)
ā”œā”€ā”€ service.py             ← orchestrator: wires everything together
ā”œā”€ā”€ main.py                ← entry point with signal handling
ā”œā”€ā”€ admin/                 ← local admin CLI (SSH-based, run on your machine)
│   ā”œā”€ā”€ admin_config.py    ← per-user config (~/.config/polymarket-watcher/admin.yaml)
│   ā”œā”€ā”€ cli.py             ← Click-based CLI (init/status/logs/restart/config)
│   ā”œā”€ā”€ editor.py          ← cross-platform editor selection ($EDITOR / VS Code / nano)
│   ā”œā”€ā”€ ssh.py             ← ssh/scp subprocess helpers
│   └── tui.py             ← streaming log viewer (journalctl -f over SSH)
ā”œā”€ā”€ watchers/
│   ā”œā”€ā”€ base_watcher.py          ← abstract BaseWatcher
│   └── price_support_watcher.py ← detects bid-support drop alerts
└── actions/
    ā”œā”€ā”€ base_action.py    ← abstract BaseAction
    └── log_action.py     ← placeholder: logs alert payload to stdout

Data Flow

  1. Startup — WatcherService loads Config, resolves the market slug to two CLOB token IDs (YES + NO) via the Gamma REST API, then instantiates all configured watchers.

  2. Connection — PolymarketWebSocketClient opens a persistent WebSocket to wss://ws-subscriptions-clob.polymarket.com/ws/market and sends a subscription frame containing both token IDs.

  3. Inbound events — The API sends two kinds of events:

    • book — full order-book snapshot (sent on subscription and after major state changes).

    • price_change — incremental update to one or more price levels.

  4. Dispatch — WatcherService._dispatch_event fans each event out to every registered watcher. Watchers that declare supported_event_types are skipped for irrelevant events (performance optimisation).

  5. Watchers — Each watcher maintains its own internal state (e.g. a local OrderBook copy) and fires Actions when its condition is met.

  6. Actions — Each action receives a structured event_data dict and performs a side-effect (log, SMS, Discord message, etc.).

Ideas for Future Watchers

Watcher

Trigger

Data source

SmartMoneyWatcher

Large single orders above threshold

price_change / CLOB WS

MarketDisputeWatcher

Dispute / resolution event

Polymarket REST API

LiquidityDepthWatcher

Spread or depth crosses threshold

book event

ExternalPlatformWatcher

Correlated price movement on Kalshi / Metaculus

External REST polling

VolumeSpikeWatcher

24-h volume spike vs rolling average

Gamma REST API