How each-way betting works on Betfair Exchange, how place markets differ from win markets in the API, how to identify them programmatically, and what this means for price analysis.
Each-way betting splits a bet between two markets: a win bet at full odds and a place bet at a fraction of the win odds. On Betfair Exchange, these are separate markets — the win market and the place market — rather than a combined bet. Understanding how to identify them, how their prices relate, and how the exchange calculates place terms is essential for any strategy that spans both markets or analyses implied probabilities.
On Betfair, a traditional each-way bet from a bookmaker is represented as two independent Exchange markets:
marketType = MATCH_ODDS. The full win market. One winner.marketType = PLACE. One or more runners can finish in the money. Prices are lower than win prices.They share the same event but have separate market IDs, separate order books, and settle independently.
When cataloguing markets, filter by marketType to find both:
MarketFilter filter = new MarketFilter()
.withEventTypeIds(Set.of("7")) // horse racing
.withMarketTypes(Set.of("MATCH_ODDS", "PLACE"))
.withMarketCountries(Set.of("GB"))
.withBspOnly(false)
.withMarketStartTime(new TimeRange()
.withFrom(Date.from(Instant.now()))
.withTo(Date.from(Instant.now().plus(Duration.ofHours(2)))));
List<MarketCatalogue> catalogues = exchange.listMarketCatalogue(
filter,
Set.of(MarketProjection.MARKET_DESCRIPTION, MarketProjection.RUNNER_METADATA,
MarketProjection.EVENT),
null,
100
);
Group by event ID to pair win and place markets for the same race:
Map<String, List<MarketCatalogue>> byEvent = catalogues.stream()
.collect(Collectors.groupingBy(mc -> mc.getEvent().getId()));
byEvent.forEach((eventId, markets) -> {
Optional<MarketCatalogue> win = markets.stream()
.filter(m -> "MATCH_ODDS".equals(m.getDescription().getMarketType()))
.findFirst();
Optional<MarketCatalogue> place = markets.stream()
.filter(m -> "PLACE".equals(m.getDescription().getMarketType()))
.findFirst();
win.ifPresent(w -> place.ifPresent(p -> processPair(w, p)));
});
The number of places paid and the fraction paid on each place are in the market description:
MarketDescription desc = catalogue.getDescription();
int numberOfWinners = desc.getNumberOfWinners(); // places paid
double eachWayDivisor = desc.getEachWayDivisor(); // e.g. 4.0 = 1/4 odds
For a 5-runner race, typically 2 places at 1/4 odds. For a 16-runner handicap, 4 places at 1/4. For a 7-runner race, 3 places at 1/5. These terms vary and are set by Betfair — always read them from the API rather than hard-coding.
In an efficient market, the place market price for a runner should be derivable from the win price and the field composition. For a 2-place market with n runners:
public double expectedPlacePrice(double winPrice, int runners, int places) {
double winProbability = 1.0 / winPrice;
// Harville formula for place probability (simplified)
double placeProbability = winProbability * places; // rough estimate
placeProbability = Math.min(placeProbability, 0.98); // cap at near-certainty
return Math.max(1.01, 1.0 / placeProbability);
}
In practice, place markets frequently misprice relative to win markets — especially on outsiders in large fields where the public over-backs win prices without considering place value. This divergence is a potential signal.
Compare win and place market prices to detect mispricing:
public record WinPlaceRatio(
long selectionId,
String runnerName,
double winPrice,
double placePrice,
double eachWayDivisor,
double impliedPlacePrice,
double deviation
) {
public static WinPlaceRatio compute(RunnerBook win, RunnerBook place,
double divisor, int numPlaces, int numRunners) {
double wp = bestBack(win);
double pp = bestBack(place);
double implied = computeImpliedPlacePrice(wp, numPlaces, numRunners);
return new WinPlaceRatio(
win.getSelectionId(), null, wp, pp, divisor, implied, pp - implied);
}
public boolean isValueInPlace() {
return deviation > 0.5; // place market offers more than implied
}
}
Subscribe to both the win and place market IDs in a single streaming subscription:
MarketSubscriptionMessage sub = new MarketSubscriptionMessage(
1, "SUBSCRIBE",
new MarketFilter(List.of(winMarketId, placeMarketId), null, null),
new MarketDataFilter(List.of("EX_BEST_OFFERS"), 3),
null, null
);
Maintain separate CondensedOrderBook instances for each market and cross-reference by selectionId. Both markets share the same selection IDs for their runners, making correlation straightforward.
Win market: one selection wins, all others lose. Place market: multiple selections can finish in the money. Settlement is automatic — Betfair settles based on the official result.
A lay on the place market is more risky than a win market lay for the same runner — multiple selections can beat you, not just one winner. Price your place lays accordingly.
With win and place markets modelled separately but correlated by event and selection, you have the foundation for each-way analysis, cross-market arbitrage detection, and informed position sizing.
If you’re building Betfair trading analysis tools in Java and want help with market data modelling, get in touch.