DEDUPE AND COOLDOWNS
Deduplicating Pinnacle drop alerts.
One falling price can produce several alerts from the Pinnacle odds API: the engine reports each fall that crosses your threshold, live markets move every few seconds, and a restart can hand you a batch you had already handled. Three keys, one cooldown rule and a small state store turn that into one alert per decision.
Why one selection alerts more than once
The stream pushes every fall at or above min_drop inside its
own detection window. A home price that goes 2.10, then 1.98, then 1.87
over twenty minutes crosses 5% twice from two different starting points,
and both crossings are real. In play, the same selection can reprice on
every point of a tennis match, so a 5% rule fires again and again while
the favourite runs away with the set.
Restarts add a third source. If your process records its position before it finishes handling a batch, the alerts in that batch are handled again when it comes back. The fix is ordering, not filtering: mark an alert done only after the message that depends on it has gone out.
Three keys, three questions
Each key answers a different question, and a relay usually needs all three.
-
The alert key is the
idfield. Two alerts with the same id are the same alert; keep the ids you have handled for a day and skip repeats. This catches the restart case exactly. -
The selection key is the match plus the market plus the
side:
home,away,league,sect,outcomeand the period when the market has one. Two alerts with the same selection key describe the same price falling further. This is the key the cooldown runs on. - The event key is the match alone. Use it when one message per match is enough, for a channel that wants to know which games are moving rather than which side.
The cooldown rule
After an alert for a selection goes out, ignore further alerts for that selection until a cooldown ends or the price has fallen by another full threshold from the price you last reported, whichever comes first. The second clause matters: a cooldown alone would hide a 2.10 to 1.70 collapse behind a 2.10 to 1.98 message sent a minute earlier.
state[key] = {last_price, alerted_at}
on alert for key:
seen = state.get(key)
if seen is None: send; store; return
fresh = now - seen.alerted_at < COOLDOWN
further = (seen.last_price - to_price)
/ seen.last_price >= MIN_DROP
if fresh and not further: return
send; store
Ten minutes suits prematch markets, where a line drifts over an afternoon. Sixty seconds suits live markets, where the same rule would otherwise swallow a whole set of tennis. Run live and prematch on separate values, since they arrive on separate streams anyway. To see what a given percentage means in probability terms before you pick a threshold, the dropping odds calculator on pnclDATA turns any pair of prices into the shift the market made.
A store that survives restarts
The state is tiny: one small record per selection you have alerted on,
and a set of alert ids. Redis with a 24-hour expiry fits, and so does a
SQLite table with an expires_at column swept once an hour.
Load it at startup before opening the stream, so the first alerts after a
restart are judged against yesterday's messages rather than a blank
memory. A prematch slate on a busy day produces low thousands of
selections, far below anything that needs tuning.
Batching what arrives together
Alerts arrive in lists, and one event often appears with two or three outcomes in the same list when a market reprices as a whole. Group by the event key inside each batch and send one message that lists every side that moved. Across batches, a five-second buffer collects the stragglers without delaying anyone noticeably. The delivery side of this, including what the chat platforms do when you post too fast, is on the webhook relay page.
Measure the effect
Count alerts read and alerts sent, per stream, per day. A relay that sends a fifth of what it reads is behaving; one that sends nearly everything has a cooldown that never bites, and one that sends almost nothing has a threshold set too high for the sports it watches. Log the two counts once an hour and the right settings show up within a week.
Questions this rule raises
Does the stream deduplicate anything itself?
It does not repeat an alert with the same id, but every new crossing of the threshold is a new alert with a new id. Deduplication by selection is your job.
What about prices that rise?
The drop stream only reports falls. A selection whose price rises back after a drop makes no sound; if you need both directions, poll the market with REST or take the raw WebSocket frames.
Should the cooldown be per sport?
Per phase first, per sport second. Live and prematch differ far more than tennis and soccer do. Start with one live value and one prematch value, then tune a sport only when its log shows a problem.
Stream behaviour follows the vendor's published documentation, checked on 26 September 2026. Published .