Arbitrage betting is often introduced with one attractive promise: cover every outcome and lock in a return.
The formula is real. The word lock is where things get complicated.
A calculation can prove that a set of quoted prices forms a theoretical arbitrage. It cannot prove that those prices are still available, that the markets settle identically, that every stake will be accepted, or that an exchange order will fill completely.
That makes arbitrage an interesting systems problem. The arithmetic is deterministic, but the inputs arrive from changing external systems and execution happens through multiple independent venues.
This article develops the calculation, implements it in JavaScript, and then examines the validation and state-management work required to turn detected prices into a fully covered position.
This is an educational discussion of market mathematics and software design, not financial or betting advice. Laws, taxes, age requirements, and operator rules vary by jurisdiction.
The Mathematical Condition
Assume a market has mutually exclusive outcomes with decimal odds:
O₁, O₂, ... Oₙ
Calculate the reciprocal sum:
S = 1/O₁ + 1/O₂ + ... + 1/Oₙ
Interpret the result as follows:
-
S < 1: theoretical arbitrage before costs and execution constraints -
S = 1: theoretical break-even before costs -
S > 1: no full-coverage arbitrage at those prices
The theoretical gross return on the deployed capital is:
ROI = (1/S - 1) × 100%
This is not always the same number as the simple gap between the reciprocal sum and 100%. A tool should label the two quantities clearly.
A Two-Outcome Example
Suppose the best available prices are:
- Outcome A:
2.10 - Outcome B:
2.05
The reciprocal sum is:
1/2.10 + 1/2.05
= 0.47619 + 0.48780
= 0.96399
Because 0.96399 < 1, these quotes form a theoretical arbitrage.
The gross theoretical ROI is approximately:
(1/0.96399 - 1) × 100% ≈ 3.735%
Equal stakes would not produce equal payouts because the odds differ.
For a total stake T, allocate each position using:
Stakeᵢ = T × (1/Oᵢ) / S
With 1,000 units of total capital, the unrounded allocation is approximately:
| Outcome | Odds | Stake | Gross payout |
|---|---|---|---|
| A | 2.10 | 493.98 | 1,037.36 |
| B | 2.05 | 506.02 | 1,037.34 |
The small payout difference comes from rounding.
Real execution may introduce larger differences through stake increments, limits, partial fills, and accepted-price changes.
Implementing the Calculator in JavaScript
The core calculation is short:
function calculateArbitrage(odds, totalStake) {
if (!Array.isArray(odds) || odds.length < 2) {
throw new Error("Provide at least two outcomes");
}
if (odds.some((price) => !Number.isFinite(price) || price <= 1)) {
throw new Error(
"Decimal odds must be finite numbers greater than 1"
);
}
if (!Number.isFinite(totalStake) || totalStake <= 0) {
throw new Error("Total stake must be a positive number");
}
const reciprocalSum = odds.reduce(
(sum, price) => sum + 1 / price,
0
);
const positions = odds.map((price, index) => {
const stake = totalStake * (1 / price) / reciprocalSum;
const payout = stake * price;
return {
outcome: index + 1,
odds: price,
stake,
payout,
profit: payout - totalStake,
};
});
return {
isTheoreticalArbitrage: reciprocalSum < 1,
reciprocalSum,
reciprocalPercentage: reciprocalSum * 100,
theoreticalRoi: (1 / reciprocalSum - 1) * 100,
positions,
};
}
console.log(calculateArbitrage([2.10, 2.05], 1000));
This function answers a narrow question:
What position follows from these inputs?
It does not establish that the inputs describe executable contracts.
That boundary should remain explicit in the software.
Detection Is Not Execution
A useful arbitrage system distinguishes at least three states:
- Detected: Observed prices produce a reciprocal sum below one.
- Executable: The event, market, rules, current prices, limits, balances, and liquidity have been verified.
- Completed: Every leg is accepted or matched at the intended size, and final exposure has been recalculated from confirmed values.
The first state can be identified from a data feed.
The second requires validation against live venue state.
The third requires acknowledgements from every venue.
A scanner result should therefore be treated as a candidate, not as a completed transaction.
Market Identity Is Part of the Data Model
Two price records are compatible only if they refer to the same contract. Matching names is not enough.
A robust market key may need fields such as:
const marketIdentity = {
sport: "football",
competition: "example-league",
eventId: "home-v-away-2026-09-23",
scheduledStart: "2026-09-23T19:00:00Z",
period: "regulation-time",
marketType: "match-result",
line: null,
participant: null,
includesOvertime: false,
settlementVersion: "operator-rules-2026-09",
};
The exact schema depends on the sport and market.
The important point is that period, line, participation conditions, overtime, void treatment, and other settlement rules are not peripheral metadata. They define the payoff states assumed by the formula.
If two legs can settle differently after the same real-world event, they are not clean complements.
Quotes Need Timestamps and Provenance
Every quote should carry enough information to answer:
- Where did the price come from?
- When was it observed?
- Is it pre-match or live?
- How much is available at that price?
- Has the source suspended or changed the market?
For example:
const quote = {
venue: "example-operator",
marketKey: "event:123:regulation:moneyline:away",
decimalOdds: 2.10,
observedAt: "2026-09-23T18:42:10.120Z",
availableSize: 250,
status: "open",
};
The system can then apply a maximum quote age or require manual verification before execution.
This does not make stale prices impossible, but it makes freshness a visible control rather than an unstated assumption.
Model the Execution as a State Machine
Execution involves transitions that can fail independently.
A position might move through states like:
DETECTED
→ VALIDATED
→ LEG_1_SUBMITTED
→ LEG_1_CONFIRMED
→ LEG_2_SUBMITTED
→ FULLY_COVERED
→ SETTLED
Failure states matter just as much:
PRICE_CHANGED
LIMITED
REJECTED
PARTIALLY_FILLED
SUSPENDED
MARKET_MISMATCH
Thinking in states prevents a dangerous simplification: treating a submitted request as a confirmed position.
For each accepted or matched leg, store the actual values:
- Requested stake
- Accepted or matched stake
- Requested price
- Accepted or weighted-average matched price
- Confirmation timestamp
- Unmatched remainder
- Required liability for lay positions
The final exposure calculation must use these confirmed values, not the original scanner snapshot.
Rounding Must Be Audited
The calculator above returns fractional stakes.
Venues usually accept only specific increments, so production code needs a rounding policy.
Rounding each stake independently can create uneven payouts. A safer workflow is:
- Calculate ideal stakes at full precision.
- Generate valid nearby stakes using each venue’s increment.
- Calculate the net result under every outcome.
- Select an allocation only if its worst-case result remains acceptable.
The key metric after rounding is not average profit. It is the minimum net outcome across all covered states.
function auditOutcomes(positions, totalCommitted) {
return positions.map(({ outcome, stake, odds }) => ({
winningOutcome: outcome,
netResult: stake * odds - totalCommitted,
}));
}
In a real implementation, subtract applicable commission, fees, taxes, and currency costs from the relevant cash flows before evaluating the minimum.
Exchanges Add Partial-Fill Complexity
An exchange quote combines price and available size.
If 100 units are requested but only 20 match at the intended price, the remaining 80 units are not covered.
Multiple fills also require a weighted execution price:
Weighted price =
Σ(fill stake × fill odds) / Σ(fill stake)
For lay orders, exposure cannot be tracked by stake alone.
Liability for each fill is:
Liability = (lay odds - 1) × lay stake
If fills occur at several prices, calculate liability for each fill and aggregate the actual market outcomes.
An order that remains unmatched also needs a cancellation or recalculation rule. Leaving it open is itself a risk decision.
A Practical Validation Pipeline
Before the first leg:
- Confirm that every record refers to the same event.
- Match the exact market, period, line, and selection.
- Compare settlement, void, and participation rules.
- Verify each price at the venue.
- Check balances, limits, maximum payout, liquidity, and liability.
- Recalculate using current prices and known costs.
- Decide the execution order.
- Define responses to movement, rejection, or partial matching.
After each leg:
- Record the accepted price and stake.
- Verify the event, selection, market, and line again.
- For exchange orders, record matched size, weighted price, and unmatched size.
- Recalculate the remaining position before continuing if any value differs from the plan.
After all legs:
- Calculate the net result under every possible covered outcome.
- Use confirmed stakes and prices.
- Include relevant commission and known costs.
- Flag any negative or unexpectedly uneven outcome for immediate review.
Metrics Beyond Realized Profit
Realized profit alone provides weak feedback about system quality.
A useful execution log can also track:
- Detected-to-executed conversion rate
- Displayed versus realized ROI
- Average executable stake
- Opportunities lost to price movement
- Rejected or limited legs
- Partial-fill frequency
- Settlement and void issues
- Errors by type
- Capital turnover and idle balances
- Net return after known costs
These metrics help locate the real bottleneck.
If many candidates fail live verification, better freshness filters may matter more than faster interaction.
If stake limits dominate, headline opportunity count may be less useful than executable capacity.
The Engineering Lesson
The arbitrage formula is the smallest component of a reliable workflow.
The harder work is maintaining contract identity, quote freshness, venue-specific constraints, execution state, and outcome-level exposure while external systems change independently.
That leads to a useful design principle:
Separate discovery data from confirmed execution data, and never let a theoretical calculation silently become an operational claim.
GoldEdge approaches arbitrage as a data and verification workflow: surface candidate price relationships first, then verify the live market before treating them as executable.
Whatever tooling you use, the final calculation should always reflect the position that was actually accepted or matched.
What would you model next: rounding optimization, exchange commission, or a full execution state machine?
Top comments (0)