Desktop app that pulls real-time sports odds from 20+ US bookmakers and surfaces +EV bets, arbitrage opportunities, and middles.
Built with Tauri 2, Svelte 5,
and Rust. Ported from the original Python Textual TUI (preserved under
python-legacy/ as a reference implementation).
Grab the latest build from the Releases page.
OddsDesk_X.Y.Z_aarch64.dmg.xattr -dr com.apple.quarantine /Applications/OddsDesk.app
Intel Macs aren't shipped as a prebuilt binary — build from source (instructions below).
OddsDesk_X.Y.Z_x64_en-US.msi.Not shipped yet. Build from source instructions below.
OddsDesk needs an API key from The Odds API.
⚠️ The free plan (500 credits/month) will not work. The Odds API bills
markets × regionsper/oddscall and per event on/events/{id}/odds(used for props). A polling tool like this — watching multiple sports, books across several regions, and refreshing every minute or two — burns through hundreds of credits per hour even on a conservative config. The $30/month (20k credits) tier is the practical floor for main-lines only; if you want player props enabled or a wide region list, you'll need one of the higher paid tiers. Tuneregions_games,regions_props,odds_refresh_interval, andprops_marketsinsettings.yamlto control burn rate.
Sign up at the-odds-api.com and copy your API key.
Launch OddsDesk once to let it create its config directory, then edit the files inside:
| OS | Config directory |
|---|---|
| macOS | ~/Library/Application Support/com.oddsdesk.app/ |
| Windows | %APPDATA%\com.oddsdesk.app\ |
Create a .env file in that directory with:
ODDS_API_KEY=your_key_here
Edit settings.yaml in the same directory to choose your sports,
bookmakers, refresh intervals, EV threshold, etc. (You can also edit
most of these live from the in-app Settings drawer — press s.)
Restart the app. You should see live odds populate.
l key).settings.yaml.Press s to open the Settings drawer. Every key from settings.yaml is
editable live (except the API key, which is managed via .env).
| Key | Action |
|---|---|
p |
Toggle between games and player props |
e |
Toggle +EV panel |
a |
Toggle arbitrage panel |
m |
Toggle middles panel |
s |
Toggle settings drawer |
l |
Toggle alternate lines |
r |
Force refresh current sport |
← / → |
Previous / next sport |
1 / 2 / 3 |
Games: moneyline / spread / total |
f |
Games: cycle game filter (ALL → UPCOMING → LIVE → FINAL) |
t |
Props: cycle market filter |
/ |
Props: focus search |
Prereqs: Node 22+, pnpm 10+, Rust stable.
git clone https://github.com/<your-org>/oddsdesk.git
cd oddsdesk
pnpm install
pnpm tauri dev # hot-reloading dev build
pnpm tauri build # release build → src-tauri/target/release/bundle/
In dev mode the app reads settings.yaml and .env from the repo root
instead of the per-user config directory.
All settings live in settings.yaml. Most fields are editable from the
in-app Settings drawer; the full list with defaults:
| Setting | Default | Description |
|---|---|---|
sports |
NFL, NBA, MLB, NHL, NCAAB | Which sports to show as tabs |
bookmakers |
20+ US books | Books to compare odds across |
regions_games |
us, us2, us_ex | Regions for game-line /odds + alt-lines |
regions_props |
us, us2, us_dfs | Regions for player-prop /events/{id}/odds |
odds_refresh_interval |
120 | Seconds between odds refreshes |
scores_refresh_interval |
60 | Seconds between scores refreshes |
props_refresh_interval |
300 | Seconds between props refreshes |
props_max_concurrent |
5 | Max parallel event fetches for props |
ev_threshold |
2.0 | Minimum EV% to flag a bet |
ev_odds_min / ev_odds_max |
-200 / 200 | Restrict EV flagging to a price range |
odds_format |
american | american or decimal |
arb_enabled |
true | Run arb detection |
arb_min_profit_pct |
0.1 | Minimum profit % to surface an arb |
middle_enabled |
true | Run middles detection |
middle_min_window |
0.5 | Minimum point window for a middle |
middle_max_combined_cost |
1.08 | Max combined implied prob for middles |
dfs_books |
{} | DFS book → effective odds overrides |
props_markets |
per-sport | Which prop markets to fetch per sport |
low_credit_warning |
50 | Show warning at this credit balance |
critical_credit_stop |
10 | Pause API calls at this balance |
+EV: for each outcome, the engine averages the implied probabilities
across every book offering it, normalizes to remove the vig (so the pair
sums to 1), and compares each book's actual price to that fair probability.
If the best price at a book yields EV ≥ ev_threshold, it's flagged.
Arbitrage: at the same line, if the best prices on opposite outcomes across different books have implied probabilities summing to less than 1, there's a guaranteed-profit arb. Stake sizing equalizes payout across both legs.
Middles: cross-line opportunities where Book A offers Over 220.5 and Book B offers Under 222.5, creating a window (221 or 222 totals land both legs). Hit probability is estimated from sport-specific point density.
First desktop release is v0.1.0 — functional, unsigned, personal-use.
A follow-up pass (Phase 3b in tasks/todo.md) will fix five known engine bugs
identified during the Rust port:
min_books filter applying across all outcomes instead of per-outcomeamerican_to_decimal(0) silent fallbackThese are preserved for parity with the Python reference in v0.1.0.
MIT. See LICENSE.