Alert on line move
Detect when a quote moves more than n% from its previous value. Useful for early-line tracking, sharp-action signals, or simply “tell me when the Lakers spread moves a half-point”.
Minimal version
Section titled “Minimal version”import { createClient, type SelectionId } from 'realtimeodds'
const client = createClient({ url: 'wss://api.realtimeodds.xyz', apiKey: process.env.REALTIMEODDS_API_KEY!})
const lastPrice = new Map<SelectionId, number>()const THRESHOLD = 0.02 // alert on >2% move
client.on('odds:changed', ({ bookmaker, sportEventId, selectionId, quote }) => { const previous = lastPrice.get(selectionId) lastPrice.set(selectionId, quote.price)
if (previous == null) return // first sighting — no comparison const move = (quote.price - previous) / previous
if (Math.abs(move) >= THRESHOLD) { const direction = move > 0 ? '↑' : '↓' console.log( `[ALERT] ${bookmaker} ${sportEventId} ${selectionId}: ${previous.toFixed(2)} → ${quote.price.toFixed(2)} (${direction} ${(move * 100).toFixed(1)}%)` ) }})
await client.connect()Filter to a specific match
Section titled “Filter to a specific match”If you only care about one event, drop everything else early:
const watchedEventId = 'vmid:ps3838:1610547234'
client.on('odds:changed', payload => { if (payload.sportEventId !== watchedEventId) return // … threshold check as above})Filter to a specific market kind
Section titled “Filter to a specific market kind”Drop player props or only watch the moneyline by reading the parent market:
client.on('odds:changed', payload => { const ev = client.getSportEvent(payload.sportEventId) const market = ev?.getMarket(payload.marketId) if (market?.kind !== 'market:basketball_match.moneyline') return // … alert logic})Cooldown
Section titled “Cooldown”Without throttling, a flickering market can fire dozens of alerts. Add a per-selection cooldown:
const lastAlertAt = new Map<SelectionId, number>()const COOLDOWN_MS = 30_000
function shouldAlert(selectionId: SelectionId): boolean { const now = Date.now() const last = lastAlertAt.get(selectionId) ?? 0 if (now - last < COOLDOWN_MS) return false lastAlertAt.set(selectionId, now) return true}Wrap the alert in if (shouldAlert(selectionId)) { … }.
Direction-only alerts
Section titled “Direction-only alerts”If you want to fire only when the line moves toward a specific team — say, Lakers shortening from +5 to +3:
import type { BasketballHandicap } from 'realtimeodds'
client.on('odds:changed', payload => { const ev = client.getSportEvent(payload.sportEventId) const market = ev?.getMarket(payload.marketId) if (market?.kind !== 'market:basketball_match.handicap') return
const handicap = (market as BasketballHandicap).handicap const previous = lastHandicap.get(payload.marketId) lastHandicap.set(payload.marketId, handicap)
if (previous != null && handicap > previous) { // line moved toward home team }})Send to Slack / Discord / email
Section titled “Send to Slack / Discord / email”Replace the console.log with a webhook call. Keep it out of the listener path — push the event onto a queue and drain asynchronously to avoid backpressure on the event loop:
const alertQueue: Alert[] = []
client.on('odds:changed', payload => { // … threshold/cooldown logic alertQueue.push({ ...payload, ts: Date.now() })})
setInterval(async () => { const batch = alertQueue.splice(0) for (const alert of batch) { await fetch(SLACK_WEBHOOK, { method: 'POST', body: JSON.stringify({ text: format(alert) }) }) }}, 1000)This keeps the listener fast — see Filtering & performance for more on this pattern.