# Bookmaker odds feeds: validate before comparing

Source: https://ipvolt.com/blog/bookmaker-odds-feed-validation
Markdown: https://ipvolt.com/blog/bookmaker-odds-feed-validation.md

[Home](https://ipvolt.com/index.md) / [Blog](https://ipvolt.com/blog.md) / Bookmaker odds feeds: validate before comparing

Category: Analysis
Published: 2026-09-14
Updated: 2026-09-14
Author: ipvolt
Reading time: 7 minutes
Tags: proxies, troubleshooting

Compare bookmaker odds only after checking market identity, settlement rules, timestamps and status. Use a local validator to expose false comparisons.

Before comparing two bookmakers' prices, establish that both observations describe the same market and an acceptable state of that market. A larger decimal number is a poor signal when it belongs to a different line, includes extra time, or was retained after suspension.

This article is for developers building odds archives, comparison displays and data-quality alerts from feeds they are authorized to use. The practical output is a comparison contract: a normalized record, explicit rejection reasons and a small offline checker. It validates your supplied evidence; it does not establish that an offer is currently executable.

## Define the market before comparing the number

Consider two fictional records: Over 2.5 goals at 1.91 and Over 3.5 goals at 2.05. Treating the latter as an improvement compares two different outcomes. The numbers can both be valid while the comparison is invalid.

Use an explicit identity key. The following is a proposed internal model, not a claim that every provider returns these field names:

| Field | What to establish | Example mistake it prevents |
| --- | --- | --- |
| Event namespace and ID | The same event in one provider namespace, or a reviewed cross-provider mapping | Joining two fixtures just because their team names match |
| Period | First half, regulation time or another defined period | Mixing a half-time market with a full-match market |
| Market and line | Exact proposition and threshold, with a consistent unit | Mixing totals 2.5 and 3.5 |
| Selection set | The same complete set of outcome identifiers | Treating a missing outcome as a price disappearance |
| Settlement rules | A reviewed equivalence group for the rules that affect the result | Mixing regulation-only and extra-time-inclusive outcomes |
| Event phase | A known pre-match or in-play state | Comparing a retained pre-match snapshot with an in-play observation |

A matching in-play flag does not establish equal score, match clock or game state. This example does not validate those fields; a workflow that requires synchronized game state needs additional evidence and gates.

Preserve the provider and bookmaker identifiers alongside that key. Identical raw IDs from different namespaces are not a cross-provider mapping. A `settlement_rules` label is only as reliable as the rule comparison behind it; copying the same label into both rows cannot establish equivalence.

These distinctions map to real feed structures. The Odds API v4 documents bookmaker and market objects, outcome prices, and `point` values for spread and totals outcomes. Its event-odds response also carries market-level update timestamps. Normalize the endpoint you actually use rather than copying a sample from a different operation. [The Odds API v4](https://the-odds-api.com/liveapi/guides/v4/)

## Rebuild state before judging completeness

A stream update is not necessarily a complete snapshot. Sportradar's Unified Odds Feed documentation says an `odds_change` can cover only some markets; markets omitted from that message remain unchanged. Clearing your entire market cache on each message would manufacture disappearing prices. [Sportradar odds-change semantics](https://docs.sportradar.com/uof/data-and-features/messages/event/odds-change)

Separate two jobs:

1. A provider adapter applies the documented snapshot, delta, recovery and status rules to a cache.
2. A comparison gate evaluates the resulting normalized state.

The checker linked below performs the second job. It must receive a complete normalized observation. Setting `complete_snapshot: true` on a raw delta bypasses the very question the adapter must answer.

For a totals example, require both `OVER` and `UNDER` in the normalized selection set. A different market needs its own completeness definition. Preserve explicit suspension, closure and unknown state; do not fill a missing status with `OPEN` unless the source contract actually establishes that state. Some protocols define defaults, but a default from one protocol is not a rule for another.

When a connection is lost, retain the last observation for your archive and mark it ineligible for current comparison until the adapter establishes usable state again. Historical visibility and current eligibility are separate properties.

## Keep collection time separate from price time

Record when your collector received the observation and what the source timestamp means. A fresh HTTP response may contain an old price, while an unchanged price may remain valid for a long time. An age threshold is an admission policy, not proof that a price is wrong.

Do not rewrite a price timestamp when the connection emits a heartbeat. Betfair distinguishes heartbeat messages from market changes; its stream `pt` is a message publish time. Connection activity alone does not establish that every cached selection was refreshed. [Betfair Exchange Stream API](https://betfair-developer-docs.atlassian.net/wiki/spaces/1smk3cen4v3lu3yomq5qye0ni/pages/2687396/Exchange%2BStream%2BAPI)

For a monitoring job, define and record these limits before admitting a pair:

- Maximum source age at the declared evaluation time.
- Maximum difference between the two collection timestamps.
- How missing, ambiguous, future or timezone-free timestamps are handled.
- Which source field supports the normalized price-update time.

Our fictional fixture policy uses a 30-second age limit and a two-second collection skew. Those values make the example reproducible; they are not bookmaker requirements or suitable defaults for every live feed. A production policy must fit the feed's documented timestamp semantics and the reader's decision deadline. A market-level update timestamp must remain identified as market-level evidence; it does not establish each outcome's last price change.

Evaluate stored records against the intended historical evaluation time when replaying an archive. Comparing yesterday's fixtures with your current wall clock answers a different question.

## Use rejection reasons to locate the problem

These are illustrative diagnostic branches, not measured bookmaker failure rates:

| Observation | Comparison decision | Investigate next |
| --- | --- | --- |
| Same contract, complete open state, valid prices and acceptable timestamps | Eligible for this comparison | Compare the observations while preserving their timestamps |
| Same label, different line or settlement rules | Reject this pairing | Correct the market mapping |
| One side suspended or closed | Hold current comparison | Process the status transition; preserve history |
| Outcome missing from a supposedly complete snapshot | Hold | Check adapter completeness and recovery |
| Old price time with a recent collection time | Hold if outside your declared age policy | Inspect upstream update semantics and cache state |
| Healthy heartbeat but no established usable market state | Hold | Diagnose stream state and recovery |
| Decimal price missing, non-finite or at most one | Reject the normalized input | Check parsing and odds-format conversion |

The last branch matters when ingesting multiple odds formats. Convert using a defined adapter and validate the resulting decimal representation; do not silently treat an American-odds integer as decimal odds. The Odds API exposes an explicit odds-format choice. [Odds API format options](https://the-odds-api.com/liveapi/guides/v4/)

## Run the offline comparison gate

Save the [Python checker](https://ipvolt.com/downloads/odds-comparison/odds_compare.py) and [fictional input cases](https://ipvolt.com/downloads/odds-comparison/fixtures.json) in the same directory, then run:

```sh
python3 odds_compare.py fixtures.json
```

The supplied checker deliberately accepts only its fixture contract: regulation-time total goals at 2.5 with the documented fictional rule ID. Other markets require a reviewed extension of the contract and tests, not just replacement prices. Keep bookmaker/source provenance in your surrounding observation log.

In the executed local fixture, one of 14 pairs was eligible and 13 were held; 21 test methods passed. These are synthetic validator results, not bookmaker success or failure rates.

The input file declares a fixed evaluation time under `now` and the illustrative age/skew policy, so replay does not depend on when you run it. Each case contains two candidate normalized records, including deliberately incomplete or invalid records that should be held. The example contract is regulation-time total goals at 2.5, with `OVER` and `UNDER` selections and a fictional settlement-rule ID.

The checker returns `eligible_for_comparison` plus reasons for held pairs. Eligibility permits a data comparison under the supplied contract; it says nothing about staking, returns, liquidity or execution. Equal prices are not required.

The [method and schema](https://ipvolt.com/downloads/odds-comparison/README.md) explain how to supply your own normalized records. The [tests](https://ipvolt.com/downloads/odds-comparison/test_odds_compare.py) exercise valid comparisons and malformed or ambiguous evidence. The checker makes no network requests and includes no API adapter. Its timestamp field, `price_updated_at`, is supplied by your adapter; the checker cannot prove that the upstream field has the meaning you assigned to it.

## Turn the contract into an operational check

An HTTP success counter belongs in transport monitoring. Keep a separate denominator for comparisons admitted by the data gate. Record attempted pairs, admitted pairs and rejection reasons, including observations you could not normalize. Otherwise, an apparently clean chart can simply be hiding the difficult inputs.

Store the mapping version, source identifiers, collection time, source timestamps, status and reasons with each decision. When an alert looks wrong, that record lets you distinguish a market-mapping defect from a stale cache or a transport failure. Do not replace unknown state with zero odds or erase the previous valid observation.

A proxy can change the transport route used by an authorized collector. It cannot establish market equivalence, fill in missing selections or prove a price is current. If the evidence is incomplete, fix the data contract before increasing request volume. For network-layer diagnosis, see [proxy environment variables](https://ipvolt.com/guides/proxy-environment-variables) and [timeout troubleshooting](https://ipvolt.com/guides/proxy-timeout-troubleshooting).

Method: this is an AI-assisted synthesis of the linked provider documentation and an explicitly synthetic local validation exercise. It reports no live bookmaker collection, provider performance, betting outcome or ipvolt service trial.

[Join the ipvolt waitlist](https://ipvolt.com/#waitlist-hero). Service access is not open yet. One email when access opens. Nothing else.

## Sources

- [Odds API Documentation V4](https://the-odds-api.com/liveapi/guides/v4/)
- [List of API Betting Markets](https://the-odds-api.com/sports-odds-data/betting-markets.html)
- [Exchange Stream API](https://betfair-developer-docs.atlassian.net/wiki/spaces/1smk3cen4v3lu3yomq5qye0ni/pages/2687396/Exchange%2BStream%2BAPI)
- [Betting Enums](https://betfair-developer-docs.atlassian.net/wiki/spaces/1smk3cen4v3lu3yomq5qye0ni/pages/2687455/Betting%2BEnums)
- [Odds Change](https://docs.sportradar.com/uof/data-and-features/messages/event/odds-change)

## Know when ipvolt access opens.

ipvolt is in development. Leave your email and we’ll notify you once when access opens.

Consent: One email when access opens. Nothing else.

[Notify me](https://ipvolt.com/blog/bookmaker-odds-feed-validation#waitlist-blog-end). Use the email form on this page to join the interest list.

[Privacy](https://ipvolt.com/privacy)

## Related posts

- [Amazon listing updates: accepted is not live](https://ipvolt.com/blog/amazon-listing-update-reconciliation.md) (Analysis, Sep 14, 2026, 8 min read): Diagnose accepted Amazon listing updates by separating submitted attributes, live offers, inventory and buyability, with a practical reconciliation matrix.
- [Cloudflare AI Agent Blocks: What to Fix First](https://ipvolt.com/blog/cloudflare-ai-agent-proxy-setup.md) (Analysis, Sep 14, 2026, 9 min read): Diagnose Cloudflare failures in AI-agent jobs, configure a stable browser proxy, and reject challenges or invalid content before RAG ingestion.
- [Proxy retries: one lost response, two created jobs](https://ipvolt.com/blog/proxy-retries-duplicate-jobs.md) (Analysis, Sep 13, 2026, 8 min read): A local proxy drops a response after a POST creates a job. See when retrying duplicates work, when an idempotency key helps, and when to reconcile.

## Related guides

- [Proxy environment variables: HTTP_PROXY and NO_PROXY](https://ipvolt.com/guides/proxy-environment-variables.md): Diagnose HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NO_PROXY routing differences in curl, Python Requests and Node.js with an isolated local check.
- [Troubleshoot proxy timeouts one stage at a time](https://ipvolt.com/guides/proxy-timeout-troubleshooting.md): Separate proxy DNS, TCP, CONNECT, TLS and response delays with curl timings, then set request deadlines and decide whether a retry is safe.
- [Use a proxy with curl](https://ipvolt.com/guides/curl-proxy-setup.md): Test an HTTP proxy with curl, separate proxy authentication from destination authentication, and read connection failures without exposing credentials.

## About ipvolt

Technical analysis from the ipvolt team.

ipvolt access is not open yet.
