betbetter 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +74 -0
- data/lib/betbetter/version.rb +5 -0
- data/lib/betbetter.rb +179 -0
- metadata +54 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2206300f9adab01c910ecebf03b96d045751fff30aa1f59ed4ce965e1094d3db
|
|
4
|
+
data.tar.gz: '0086f17ac2ea570145fc6019f28d6d4e0f0785f032f6970e283d9ca3e7048974'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ec316a259a6e30d19200651c0cf64364f153df0aa15f165a6bee77645570f1d52bb4418e36d617259bad6cec5a7575d247bf8d158fc0134208e088aff2ade1f7
|
|
7
|
+
data.tar.gz: c5c9b4792e6c7f3c787525b6deeb3d73b9a8a967b1a7fe03c1e5559e79f6149ec6d2d693cc587f6aec5a23a94706073bdd0f561d4d899f86ae2c9ca5b07b90c9
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Edward Glush
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# betbetter (Ruby)
|
|
2
|
+
|
|
3
|
+
Zero-dependency Ruby client for the **free [Bet Better](https://betbetter.world/api) sports model
|
|
4
|
+
API** — win probabilities, fair odds and plain-English verdicts for every selection the models
|
|
5
|
+
publish. **No API key required.** Standard library only (`net/http`, `json`).
|
|
6
|
+
|
|
7
|
+
Bet Better was founded in 2024.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
gem install betbetter
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or in a Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "betbetter"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
require "betbetter"
|
|
25
|
+
|
|
26
|
+
Betbetter.list_leagues # => ["afl", "mlb", "nba", ... "soccer/world-cup"]
|
|
27
|
+
|
|
28
|
+
feed = Betbetter.get_picks("nba") # game lines + player props
|
|
29
|
+
feed["picks"].first(3).each do |p|
|
|
30
|
+
puts "#{p['game']} - #{p['selection']} (#{p['confidence']}, fair odds #{p['fairOdds']})"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Betbetter.get_best_bets("nfl") # game lines only
|
|
34
|
+
Betbetter.get_prop_bets("nfl") # player props only
|
|
35
|
+
Betbetter.get_results # the public settled record - wins AND losses
|
|
36
|
+
Betbetter.get_scorecard # hit rate, ROI and closing-line value by market
|
|
37
|
+
Betbetter.get_predicted_scores("nba") # array of row hashes, one per priced game
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## What you get
|
|
41
|
+
|
|
42
|
+
Every selection carries the model's own numbers — no bookmaker prices are republished:
|
|
43
|
+
|
|
44
|
+
| Field | Meaning |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `game`, `gameTimeUtc` | Fixture and UTC start time |
|
|
47
|
+
| `market`, `selection`, `line` | What the bet is, in plain words |
|
|
48
|
+
| `winProbabilityPct` | The chance we give this selection (%) |
|
|
49
|
+
| `fairOdds` | Decimal odds implied by that chance |
|
|
50
|
+
| `confidence` | `HIGH` / `LEAN` / `LONG-SHOT` |
|
|
51
|
+
| `verdict` | A dated plain-English read |
|
|
52
|
+
|
|
53
|
+
Leagues: `Betbetter::LEAGUES` — AFL, MLB, NBA, NFL, NHL, NCAAF, NRL, UFC, WNBA, cricket, ATP/WTA
|
|
54
|
+
tennis, and six soccer competitions.
|
|
55
|
+
|
|
56
|
+
## Tests
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
ruby -Ilib test/test_betbetter.rb # makes one live API call
|
|
60
|
+
BETBETTER_SKIP_LIVE=1 ruby -Ilib test/test_betbetter.rb # offline
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Links
|
|
64
|
+
|
|
65
|
+
- API docs: https://betbetter.world/api
|
|
66
|
+
- Source: https://gitlab.com/betbetterworld/betbetter-ruby
|
|
67
|
+
|
|
68
|
+
## Licence
|
|
69
|
+
|
|
70
|
+
Client code: MIT (Edward Glush, 2026).
|
|
71
|
+
|
|
72
|
+
Data: CC BY 4.0, free to reuse with a credit — *Bet Better (https://betbetter.world)*.
|
|
73
|
+
|
|
74
|
+
Model estimates, not betting advice. 18+. Gamble responsibly.
|
data/lib/betbetter.rb
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# betbetter - zero-dependency Ruby client for the free Bet Better sports model API.
|
|
4
|
+
#
|
|
5
|
+
# Win probabilities, fair odds and plain-English verdicts for every selection the
|
|
6
|
+
# Bet Better models publish. No API key required.
|
|
7
|
+
#
|
|
8
|
+
# Data licence: CC BY 4.0 - free to reuse with a credit to Bet Better
|
|
9
|
+
# (https://betbetter.world). Model estimates, not betting advice. 18+.
|
|
10
|
+
#
|
|
11
|
+
# require "betbetter"
|
|
12
|
+
# feed = Betbetter.get_picks("nba")
|
|
13
|
+
# feed["picks"].first(3).each { |p| puts "#{p['game']} - #{p['selection']} (#{p['confidence']})" }
|
|
14
|
+
|
|
15
|
+
require "net/http"
|
|
16
|
+
require "uri"
|
|
17
|
+
require "json"
|
|
18
|
+
|
|
19
|
+
require_relative "betbetter/version"
|
|
20
|
+
|
|
21
|
+
module Betbetter
|
|
22
|
+
BASE_URL = "https://betbetter.world"
|
|
23
|
+
USER_AGENT = "betbetter-ruby/#{VERSION}"
|
|
24
|
+
|
|
25
|
+
# League path segments accepted by the API (the `league` enum in the OpenAPI
|
|
26
|
+
# spec). Soccer and tennis use two segments.
|
|
27
|
+
LEAGUES = %w[
|
|
28
|
+
afl mlb nba nfl nhl ncaaf nrl ufc wnba cricket
|
|
29
|
+
tennis/atp tennis/wta
|
|
30
|
+
soccer/epl soccer/la-liga soccer/serie-a soccer/bundesliga
|
|
31
|
+
soccer/ligue-1 soccer/world-cup
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
# Sports served by /predicted-scores.
|
|
35
|
+
SCORE_SPORTS = %w[
|
|
36
|
+
nfl ncaaf nba ncaab mlb nhl afl nrl wnba soccer cricket
|
|
37
|
+
].freeze
|
|
38
|
+
|
|
39
|
+
# Raised when the API returns a non-200 response.
|
|
40
|
+
class Error < StandardError; end
|
|
41
|
+
|
|
42
|
+
class << self
|
|
43
|
+
# The leagues this API serves.
|
|
44
|
+
# @return [Array<String>]
|
|
45
|
+
def list_leagues
|
|
46
|
+
LEAGUES.dup
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Combined picks feed (game lines + player props) for a league.
|
|
50
|
+
# Each pick carries game, gameTimeUtc, market, selection, line,
|
|
51
|
+
# winProbabilityPct, fairOdds, confidence (HIGH / LEAN / LONG-SHOT) and a
|
|
52
|
+
# dated verdict.
|
|
53
|
+
# @return [Hash]
|
|
54
|
+
def get_picks(league)
|
|
55
|
+
get_json("/#{check_league(league)}/picks")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Game-line selections only for a league.
|
|
59
|
+
# @return [Hash]
|
|
60
|
+
def get_best_bets(league)
|
|
61
|
+
get_json("/#{check_league(league)}/best-bets")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Player-prop selections only for a league.
|
|
65
|
+
# @return [Hash]
|
|
66
|
+
def get_prop_bets(league)
|
|
67
|
+
get_json("/#{check_league(league)}/prop-bets")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The settled record - wins and losses - with hit rate and ROI by sport.
|
|
71
|
+
# @return [Hash]
|
|
72
|
+
def get_results
|
|
73
|
+
get_json("/results")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Settled model performance by sport and market (hit rate, ROI, CLV).
|
|
77
|
+
# @return [Hash]
|
|
78
|
+
def get_scorecard
|
|
79
|
+
get_json("/scorecard")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Predicted final scores for every game the model prices in a sport.
|
|
83
|
+
# Served as CSV; parsed here into an array of row hashes with numeric
|
|
84
|
+
# values where the column is numeric.
|
|
85
|
+
# @return [Array<Hash>]
|
|
86
|
+
def get_predicted_scores(sport)
|
|
87
|
+
s = normalise(sport)
|
|
88
|
+
text = fetch("/predicted-scores/#{s}", "csv", "text/csv")
|
|
89
|
+
parse_csv(text)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def normalise(value)
|
|
95
|
+
value.to_s.strip.gsub(%r{\A/+|/+\z}, "").downcase
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def check_league(league)
|
|
99
|
+
lg = normalise(league)
|
|
100
|
+
unless LEAGUES.include?(lg)
|
|
101
|
+
raise ArgumentError, "Unknown league #{league.inspect}. Valid options: #{LEAGUES.join(', ')}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
lg
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def get_json(path)
|
|
108
|
+
JSON.parse(fetch(path, "json", "application/json"))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def fetch(path, format, accept)
|
|
112
|
+
uri = URI.parse("#{BASE_URL}#{path}?format=#{format}")
|
|
113
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https",
|
|
114
|
+
open_timeout: 15, read_timeout: 30) do |http|
|
|
115
|
+
http.request(Net::HTTP::Get.new(uri, "User-Agent" => USER_AGENT, "Accept" => accept))
|
|
116
|
+
end
|
|
117
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
118
|
+
raise Error, "betbetter: HTTP #{response.code} for #{path}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
response.body.to_s.force_encoding(Encoding::UTF_8)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Minimal RFC4180-ish parser - enough for this feed (quoted fields may hold
|
|
125
|
+
# commas and doubled quotes), so the gem stays standard-library only.
|
|
126
|
+
def parse_csv(text)
|
|
127
|
+
lines = text.split(/\r?\n/).reject { |l| l.empty? || l.start_with?("#") }
|
|
128
|
+
return [] if lines.length < 2
|
|
129
|
+
|
|
130
|
+
header = split_row(lines[0])
|
|
131
|
+
lines[1..].map do |line|
|
|
132
|
+
values = split_row(line)
|
|
133
|
+
row = {}
|
|
134
|
+
header.each_with_index { |col, i| row[col] = coerce(values[i]) }
|
|
135
|
+
row
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def split_row(line)
|
|
140
|
+
fields = []
|
|
141
|
+
field = +""
|
|
142
|
+
in_quotes = false
|
|
143
|
+
i = 0
|
|
144
|
+
while i < line.length
|
|
145
|
+
ch = line[i]
|
|
146
|
+
if in_quotes
|
|
147
|
+
if ch == '"'
|
|
148
|
+
if line[i + 1] == '"'
|
|
149
|
+
field << '"'
|
|
150
|
+
i += 1
|
|
151
|
+
else
|
|
152
|
+
in_quotes = false
|
|
153
|
+
end
|
|
154
|
+
else
|
|
155
|
+
field << ch
|
|
156
|
+
end
|
|
157
|
+
elsif ch == '"'
|
|
158
|
+
in_quotes = true
|
|
159
|
+
elsif ch == ","
|
|
160
|
+
fields << field
|
|
161
|
+
field = +""
|
|
162
|
+
else
|
|
163
|
+
field << ch
|
|
164
|
+
end
|
|
165
|
+
i += 1
|
|
166
|
+
end
|
|
167
|
+
fields << field
|
|
168
|
+
fields
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def coerce(value)
|
|
172
|
+
return nil if value.nil? || value.empty?
|
|
173
|
+
return Integer(value) if value.match?(/\A-?\d+\z/)
|
|
174
|
+
return Float(value) if value.match?(/\A-?\d*\.\d+([eE][-+]?\d+)?\z/)
|
|
175
|
+
|
|
176
|
+
value
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: betbetter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Edward Glush
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-09-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Win probabilities, fair odds and plain-English verdicts for every selection
|
|
14
|
+
the Bet Better models publish, across 12 sports. No API key. Data licensed CC BY
|
|
15
|
+
4.0. Model estimates, not betting advice. 18+.
|
|
16
|
+
email:
|
|
17
|
+
- edward@betbetter.world
|
|
18
|
+
executables: []
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/betbetter.rb
|
|
25
|
+
- lib/betbetter/version.rb
|
|
26
|
+
homepage: https://gitlab.com/betbetterworld/betbetter-ruby
|
|
27
|
+
licenses:
|
|
28
|
+
- MIT
|
|
29
|
+
metadata:
|
|
30
|
+
homepage_uri: https://gitlab.com/betbetterworld/betbetter-ruby
|
|
31
|
+
source_code_uri: https://gitlab.com/betbetterworld/betbetter-ruby
|
|
32
|
+
bug_tracker_uri: https://gitlab.com/betbetterworld/betbetter-ruby/-/issues
|
|
33
|
+
documentation_uri: https://betbetter.world/api
|
|
34
|
+
rubygems_mfa_required: 'true'
|
|
35
|
+
post_install_message:
|
|
36
|
+
rdoc_options: []
|
|
37
|
+
require_paths:
|
|
38
|
+
- lib
|
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: 3.0.0
|
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
requirements: []
|
|
50
|
+
rubygems_version: 3.5.22
|
|
51
|
+
signing_key:
|
|
52
|
+
specification_version: 4
|
|
53
|
+
summary: Zero-dependency Ruby client for the free Bet Better sports model API.
|
|
54
|
+
test_files: []
|