Skip to content
Table of contents

Sport-Arbitrage

Sport-Arbitrage is a small web calculator for sports-betting arbitrage: you enter the odds two betting sites give for the same match, and it tells you whether betting on opposite outcomes at both sites locks in a profit, and how to split the stake. It is an AlgoÉTS repository with a browser page and a Python notebook that run the same formulas.

Static page
HTMLJavaScriptTailwind CSSPythonJupyter
  • Updated

1. Overview

index.html is a single form styled with Tailwind CSS 2.2.19 from a CDN. All the logic is in script.js:

  • hasArbitrageOpportunity(odds) adds the inverses of the odds and reports an opportunity when the sum is below 1.
  • calculateArbitragePercentage returns (1 − Σ 1/odds) × 100.
  • calculateOptimalStakes splits the total investment in proportion to each inverse odd, so every outcome returns the same amount.
  • calculateTotalReturn and calculateGuaranteedProfit give the return per outcome and the smallest return minus the investment.
  • calculateArbitrage() reads the form, tests two cross-site pairings (win at site 1 with loss at site 2, and loss at site 1 with win at site 2) and adds a draw to each pairing in three-outcome mode, then writes one result card per opportunity.

arbitrage.ipynb contains the same functions in Python, with sample odds hard-coded in the first cell.

2. Use cases

Browser calculator that checks two bookmakers' odds for a sports-betting arbitrage and splits the stake.

Use-case diagram for Sport-Arbitrage: Pick 2 or 3 outcomes; Enter odds from two sites; Detect an arbitrage; Split the stake; Check the math in PythonSport-ArbitragePick 2 or 3 outcomesEnter odds from two sitesDetect an arbitrageSplit the stakeCheck the math in PythonBettor
People on the left, external systems on the right; each oval is a use case backed by the files cited below.
Pick 2 or 3 outcomes
Bettor

The Number of Odds selector in index.html calls toggleOddsFields() in script.js, which shows the two Draw fields only for three outcomes.

Enter odds from two sites
Bettor

The form takes Win and Lost odds for Site 1 and Site 2, optional Draw odds, and the total investment.

Detect an arbitrage
Bettor

calculateArbitrage() tests fixed cross-site pairings; hasArbitrageOpportunity() reports one when the sum of inverse odds is below 1.

Split the stake
Bettor

For each opportunity, script.js prints the arbitrage percentage, optimal stakes, returns per outcome and guaranteed profit.

Check the math in Python
Bettor

arbitrage.ipynb implements the same functions in Python with hard-coded sample odds for the two-outcome case.

2.1. Scenario: Test the notebook's sample odds

  1. Open index.html in a browser and keep Number of Odds at 2.
  2. Enter Win Site 1 = 1.3, Lost Site 1 = 3.93, Win Site 2 = 1.42 and Lost Site 2 = 2.9.
  3. Enter a Total Investment of 100 and click Calculate.
  4. Read the result: only the lost-site1 and win-site2 pair qualifies, with stakes of about 26.54 and 73.46.
  5. Compare with the guaranteed profit line (about 4.31) and rerun arbitrage.ipynb to check the same numbers.

Files readREADME.mdindex.htmlscript.jsstyle.cssarbitrage.ipynb

3. How the algorithm works

Implied probability. Decimal odds o pay o times the stake when the bet wins, stake included. The probability at which that bet breaks even is 1/o, called the implied probability. At one bookmaker, the implied probabilities of all outcomes add up to more than 1; the excess is the bookmaker’s margin. Two sites with slightly different opinions can still disagree enough that, by taking the best price for each outcome across sites, the sum falls below 1.

The arbitrage condition. For outcomes that cover every possible result, with odds o₁ … oₙ taken from any sites:

S = 1/o₁ + 1/o₂ + … + 1/oₙ
arbitrage exists when S < 1

Stake sizing. To get the same payout whichever outcome happens, each stake must be proportional to its implied probability: stakeᵢ = investment × (1/oᵢ) / S. Every outcome then pays stakeᵢ × oᵢ = investment / S, so the guaranteed profit is investment × (1/S − 1). This is exactly what calculateOptimalStakes, calculateTotalReturn and calculateGuaranteedProfit compute.

Worked example, two outcomes. Site 1 offers 2.10 on a win and 1.80 on a loss; site 2 offers 1.75 on a win and 2.05 on a loss.

StepValue
Site 1 alone: 1/2.10 + 1/1.801.0317, a 3.2% margin, no arbitrage
Site 2 alone: 1/1.75 + 1/2.051.0592
Win at site 1 + loss at site 2: 1/2.10 + 1/2.05 = 0.4762 + 0.48780.9640 < 1, arbitrage
Loss at site 1 + win at site 2: 1/1.80 + 1/1.751.1270, none
Stakes for 1,000 $: 1000 × 0.4762 / 0.9640 and 1000 × 0.4878 / 0.9640493.98 $ on the win, 506.02 $ on the loss
Payout either way: 493.98 × 2.10 = 506.02 × 2.051,037.35 $
Guaranteed profit37.35 $, or 3.73% of the investment

The page reports an “arbitrage percentage” of 3.60%, which is (1 − S) × 100. That number is the gap below 1, not the return: the return on the money staked is 1/S − 1, here 3.73%.

Three outcomes. With a draw, the same formulas take three odds. Win at 2.60 on site 1, loss at 3.40 on site 2 and draw at 3.50 on site 1 give S = 0.3846 + 0.2941 + 0.2857 = 0.9644. For 1,000 $, the stakes are 398.79 $, 304.96 $ and 296.25 $, each outcome pays 1,036.86 $, and the profit is 36.86 $.

flowchart TD
  A["Read odds: win and loss at site 1 and site 2, draws if 3-way, investment"] --> B{"Two or three outcomes?"}
  B -->|two| P1["Pair A: win site 1 + loss site 2"]
  B -->|two| P2["Pair B: loss site 1 + win site 2"]
  B -->|three| P3["Set A: win site 1 + loss site 2 + draw site 1"]
  B -->|three| P4["Set B: loss site 1 + win site 2 + draw site 2"]
  P1 & P2 & P3 & P4 --> S["S = sum of 1/odds"]
  S --> C{"S < 1?"}
  C -->|no| N["Skip this combination"]
  C -->|yes| K["stakeᵢ = investment × (1/oᵢ) / S"]
  K --> R["payoutᵢ = stakeᵢ × oᵢ = investment / S"]
  R --> G["profit = min(payout) − investment"]
  G --> O["Result card per combination"]
  N --> E{"Any card?"}
  O --> E
  E -->|no| X["No Arbitrage Opportunities Found"]

4. Getting started

Nothing needs to be built. Clone the repository and open the page in a browser (the Tailwind stylesheet loads from jsDelivr, so the page needs network access to look right):

git clone https://github.com/AlgoETS/Sport-Arbitrage.git
cd Sport-Arbitrage

Then open index.html, choose 2 or 3 odds, fill in the odds and the total investment, and click Calculate. To run the Python version:

jupyter notebook arbitrage.ipynb

5. Repository guide

  • index.html: the calculator form, instructions and result area.
  • script.js: field toggling, arbitrage test, stake split, returns and profit display.
  • arbitrage.ipynb: Python version of the formulas for the two-outcome case.
  • style.css: basic layout styles (not referenced by index.html).

6. Features

  • switch between two-outcome (win/loss) and three-outcome (win/loss/draw) markets;
  • test cross-site pairings for an arbitrage with the inverse-odds sum;
  • show the arbitrage percentage, optimal stakes, return per outcome and guaranteed profit;
  • run entirely in the browser, with no server or account;
  • check the same math in a Jupyter notebook.

7. Limits

  • Only two fixed pairings are tested. In three-outcome mode, the draw from site 1 always joins win at site 1 and loss at site 2, and the draw from site 2 joins loss at site 1 and win at site 2; other mixes are never tried.
  • The “arbitrage percentage” shown is (1 − S) × 100, slightly lower than the actual return 1/S − 1.
  • The formulas assume decimal odds, and the form does not validate input: empty fields produce NaN.
  • Stakes are not rounded and ignore fees, betting limits and odds changes, so the “guaranteed profit” holds only if every bet is accepted at the entered odds.
  • style.css is not linked from the page, and the notebook covers only the two-outcome case.
  • There are no tests.

8. Sources

9. CI/CD pipeline

No CI/CD pipeline in the repository. The code is built and run by hand, as described above.

Generated from the source at commit dbee188.

10. Deployment diagram

Mermaid Deployment diagram
flowchart LR
  repo[("GitHub repository")]
  local["Local machine"]
  repo -->|"clone and run"| local

The repository has no Dockerfile, Compose file or hosting configuration, so the code runs on the machine that clones it.

11. External dependencies

The repository declares no external packages: it has no manifest such as pyproject.toml, requirements.txt, package.json or pom.xml.

12. Glossary

Repository

The versioned source files, configuration, and history of a project.

README

The repository’s entry document, usually covering its purpose and initial setup.

Dependency

A library, tool, or service that another component needs to build or run.

HTTP

A request-and-response protocol used by browsers and web services. A method, path, headers, and optional body describe a request.

Volume and bind mount

A volume is storage managed by the container platform; a bind mount exposes a chosen host path inside a container.

Published port

A host port mapped to a service’s container port. Publishing a port can expose that service beyond its container network.

CLI

A text interface in which commands and arguments select operations. Exit codes let scripts detect success or failure.

Build

The steps that turn source files and dependencies into a runnable program or distributable artifact.

Mermaid

A text-based diagram language. Nodes and connections describe a model; the diagram itself does not execute the project.

13. References

  1. AlgoÉTS. (2026, November 5). [Web page] github.com.
  2. AlgoETS. (2026, November 5). Sport-Arbitrage. [Software repository] github.com.
  3. AlgoETS. (2026, November 5). README. [Source file or documentation] github.com.
  4. AlgoETS. (2026, November 5). index.html. [Source file or documentation] github.com.
  5. AlgoETS. (2026, November 5). script.js. [Source file or documentation] github.com.
  6. AlgoETS. (2026, November 5). arbitrage.ipynb. [Source file or documentation] github.com.

Sources without a publication date show the date they were last checked, November 5, 2026. File links retain the inspected revision where available.