Skip to content

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”.

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()

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
})

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
})

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)) { … }.

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
}
})

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.