Hire Me
← All Writing Betfair

Overround Calculation and Market Efficiency Scoring in Java

How to calculate the overround on a Betfair Exchange market, what it tells you about market efficiency, and how to build an efficiency score that filters for trading opportunities in Java.

The overround (or book percentage) measures how much the total implied probability in a market exceeds 100%. In a perfectly efficient market with no margin, the sum of all runners’ implied win probabilities is exactly 1.0. In a traditional bookmaker market it is typically 1.10–1.20 (10–20% over-round). On Betfair Exchange, the best-back prices typically produce a book percentage of 1.01–1.04 — the market is close to efficient, but the degree of efficiency changes over time and contains information.

Computing overround from best back prices

public class OverroundCalculator {

    public double compute(Map<Long, Double> bestBackPrices) {
        if (bestBackPrices.isEmpty()) return Double.NaN;

        double sumImplied = bestBackPrices.values().stream()
            .filter(price -> price >= 1.01)
            .mapToDouble(price -> 1.0 / price)
            .sum();

        return sumImplied;   // > 1.0 means over-round; < 1.0 means under-round
    }

    public double overroundPercent(Map<Long, Double> bestBackPrices) {
        double raw = compute(bestBackPrices);
        return Double.isNaN(raw) ? Double.NaN : (raw - 1.0) * 100.0;
    }
}

For a 6-runner market with best back prices:

Runner A: 2.50 → implied 0.400
Runner B: 3.50 → implied 0.286
Runner C: 6.00 → implied 0.167
Runner D: 9.00 → implied 0.111
Runner E: 12.0 → implied 0.083
Runner F: 20.0 → implied 0.050
Sum:             1.097 → 9.7% over-round

Lay overround

The lay side computes identically using best lay prices:

public double computeLayOverround(Map<Long, Double> bestLayPrices) {
    double sumImplied = bestLayPrices.values().stream()
        .filter(price -> price >= 1.01)
        .mapToDouble(price -> 1.0 / price)
        .sum();

    return sumImplied;   // < 1.0 for lays (under-round from layer's perspective)
}

On a healthy exchange market, back overround is slightly above 1.0 and lay overround is slightly below 1.0. The spread between them represents the exchange’s margin.

Market efficiency score

A lower overround indicates a more efficient (harder to beat) market. An unusually high overround on the back side, or unusually low on the lay side, can indicate inefficiency worth investigating:

public record MarketEfficiency(
    double backOverround,
    double layOverround,
    double spread,
    double efficiency   // 0.0 = perfect; positive = backers are getting bad value
) {
    public static MarketEfficiency compute(
        Map<Long, Double> backs, Map<Long, Double> lays) {

        double bo = new OverroundCalculator().compute(backs);
        double lo = new OverroundCalculator().computeLayOverround(lays);

        double spread = bo - lo;   // the exchange's take
        double efficiency = (bo - 1.0);   // excess above fair book

        return new MarketEfficiency(bo, lo, spread, efficiency);
    }

    public boolean isEfficient() {
        return efficiency < 0.03;   // < 3% overround = well-priced
    }
}

Efficiency over time

In the hours before a race, the market typically starts inefficient (few participants, wide spreads) and becomes progressively more efficient as volume builds. Track this trajectory:

public class EfficiencyTimeSeries {

    private final List<EfficiencyPoint> points = new ArrayList<>();

    public record EfficiencyPoint(Instant time, double efficiency, long matchedVolume) {}

    public void record(MarketEfficiency eff, long matchedVolume) {
        points.add(new EfficiencyPoint(Instant.now(), eff.efficiency(), matchedVolume));
    }

    public boolean isStabilising() {
        if (points.size() < 10) return false;

        List<EfficiencyPoint> recent = points.subList(points.size() - 10, points.size());
        double variance = computeVariance(recent.stream()
            .mapToDouble(EfficiencyPoint::efficiency).toArray());

        return variance < 0.0001;   // efficiency has stabilised
    }

    private double computeVariance(double[] values) {
        double mean = Arrays.stream(values).average().orElse(0.0);
        return Arrays.stream(values)
            .map(v -> (v - mean) * (v - mean))
            .average().orElse(0.0);
    }
}

Runner-level mispricing

Beyond the market-level overround, identify individual runners where the implied probability differs significantly from their price relative to the field:

public Map<Long, Double> computeImpliedProbabilities(Map<Long, Double> bestBackPrices) {
    double overround = new OverroundCalculator().compute(bestBackPrices);

    return bestBackPrices.entrySet().stream()
        .collect(Collectors.toMap(
            Map.Entry::getKey,
            e -> (1.0 / e.getValue()) / overround   // normalised to sum to 1.0
        ));
}

Normalising by the overround gives each runner’s implied win probability assuming the market is correctly calibrated. Compare this to an independent probability estimate (ratings, form, historical SP) to find runners where the market’s probability differs from your model.

Minimum volume threshold

Overround is only meaningful with sufficient matched volume. An early market with £500 matched can have extreme overrounds that normalise as volume builds:

public Optional<MarketEfficiency> computeIfSufficient(
    Map<Long, Double> backs, Map<Long, Double> lays, long matchedVolume) {

    if (matchedVolume < 10_000) return Optional.empty();   // too thin to trust

    return Optional.of(MarketEfficiency.compute(backs, lays));
}

Using efficiency as a filter

Before applying any price-based signal, check whether the market is sufficiently liquid and efficient:

public boolean shouldActivateStrategy(String marketId) {
    MarketState state = marketStateMap.get(marketId);
    if (state == null) return false;

    Optional<MarketEfficiency> eff = computeIfSufficient(
        state.getBestBacks(), state.getBestLays(), state.getMatchedVolume());

    return eff.map(MarketEfficiency::isEfficient).orElse(false);
}

Price-based strategies applied to thin, inefficient markets are unreliable — the signals are noisy and the spreads make execution costly. Overround as a filter lets your system focus on the markets where signal quality is highest.

If you’re building market analysis tools for Betfair in Java and want a review of your signal quality filtering, get in touch.

Samuel Jackson

Samuel Jackson

Senior Java Back End Developer & Contractor

Senior Java Back End Developer — Betfair Exchange API specialist, Spring Boot, AWS, and event-driven architecture. 20+ years delivering high-performance systems across betting, finance, energy, retail, and government. Available for Java contracting.