Evaluation
Evaluate the performance of predictive models.
BaseBettor(betting_markets=None, init_cash=None, stake=None)
Bases: MultiOutputMixin, ClassifierMixin, BaseEstimator
The base class for bettors.
A bettor turns probabilities into bets. It places a bet when the model gives an outcome a higher probability than
its price implies. To build one, implement _fit and _predict_proba.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
betting_markets
|
list[str] | None
|
Select the betting markets from the ones included in the data. |
None
|
init_cash
|
float | None
|
The initial cash to use when betting. |
None
|
stake
|
float | None
|
The stake of each bet. |
None
|
Examples:
>>> import numpy as np
>>> from sportsbet.dataloaders import DataLoader
>>> from sportsbet.sources import SampleSoccerOdds, SampleSoccerStats
>>> from sportsbet.evaluation import BaseBettor, backtest
>>>
>>> class BaseRateBettor(BaseBettor):
... 'A bettor of your own, knowing only how often each outcome has happened.'
...
... def _fit(self, X, Y, O):
... # `Y` carries the markets it was told to bet, in the order it was told them.
... self.rates_ = Y.mean().to_numpy()
... return self
...
... def _predict_proba(self, X):
... rates = np.tile(self.rates_, (len(X), 1))
... return rates / rates.sum(axis=1, keepdims=True)
>>>
>>> dataloader = DataLoader(
... param_grid={'league': ['England']}, stats=SampleSoccerStats(), odds=SampleSoccerOdds()
... )
>>> X, Y, O = dataloader.extract_train_data(odds_type='market_average')
>>> bettor = BaseRateBettor(betting_markets=['home_win', 'draw', 'away_win'])
>>> results = backtest(bettor, X, Y, O)
>>> 'Yield percentage per bet' in results.columns
True
>>> # `bet` gives the value bets, one row per match and one column per market.
>>> bettor.fit(X, Y, O).bet(X, O).shape
(380, 3)
Source code in src/sportsbet/evaluation/_base.py
172 173 174 175 176 177 178 179 180 | |
bet(X, O)
Predict the value bets for the provided input data and odds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
The input data. |
required |
O
|
DataFrame
|
The odds data. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
B |
BoolData
|
The value bets. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If the |
Source code in src/sportsbet/evaluation/_base.py
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 | |
fit(X, Y, O=None)
Fit the bettor to the input data and multi-output targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
The input data. |
required |
Y
|
DataFrame
|
The multi-output targets. |
required |
O
|
DataFrame | None
|
The odds data. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
self |
Self
|
The fitted bettor object. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If the |
Source code in src/sportsbet/evaluation/_base.py
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | |
predict(X)
Predict class labels for multi-output targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
The input data. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Y |
BoolData
|
The positive class labels. |
Source code in src/sportsbet/evaluation/_base.py
383 384 385 386 387 388 389 390 391 392 393 394 395 396 | |
predict_proba(X)
Predict class probabilities for multi-output targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
The input data. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Y |
Data
|
The positive class probabilities. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If the predicted probabilities and selected betting markets have incompatible shapes. |
Source code in src/sportsbet/evaluation/_base.py
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 | |
score(X, Y, O)
Return the annual sharpe ratio on the given data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
The input data. |
required |
Y
|
DataFrame
|
The output data. |
required |
O
|
DataFrame
|
The odds data. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
score |
float
|
Annual sharpe ratio of predicted value bets. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If the |
Source code in src/sportsbet/evaluation/_base.py
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 | |
BettorGridSearchCV(estimator, param_grid, *, scoring=None, n_jobs=None, refit=True, cv=TSCV, verbose=0, pre_dispatch='2*n_jobs', error_score=np.nan, return_train_score=False)
Bases: GridSearchCV, BaseBettor
Search a bettor's parameter grid with cross-validation.
It optimizes the bettor's parameters by cross-validated grid search over the parameter grid.
Read more in the user guide.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
estimator
|
BaseBettor
|
This is assumed to implement the bettor interface. |
required |
param_grid
|
dict | list
|
Dictionary with parameters names ( |
required |
scoring
|
str | Callable | list | tuple | dict[str, Callable] | None
|
Strategy to evaluate the performance of the cross-validated model on the test set. If
If
|
None
|
n_jobs
|
int | None
|
Number of jobs to run in parallel. |
None
|
refit
|
bool | str | Callable
|
Refit an estimator using the best found parameters on the whole dataset. For multiple metric evaluation, this needs to be a Where there are considerations other than maximum score in
choosing a best estimator, The refitted estimator is made available at the Also for multiple metric evaluation, the attributes See |
True
|
cv
|
TimeSeriesSplit
|
Provides train/test indices to split time series data samples that are observed at fixed time intervals, in train/test sets. |
TSCV
|
verbose
|
int
|
Controls the verbosity: the higher, the more messages. |
0
|
pre_dispatch
|
int | str
|
Controls the number of jobs that get dispatched during parallel execution. Reducing this number can be useful to avoid an explosion of memory consumption when more jobs get dispatched than CPUs can process. This parameter can be: |
'2*n_jobs'
|
error_score
|
str | float | int
|
Value to assign to the score if an error occurs in estimator fitting.
If set to |
nan
|
return_train_score
|
bool
|
If |
False
|
Attributes:
| Name | Type | Description |
|---|---|---|
cv_results_ |
A dict with keys as column headers and values as columns, that can be
imported into a pandas The key The For multi-metric evaluation, the scores for all the scorers are
available in the |
|
best_estimator_ |
Estimator that was chosen by the search, i.e. estimator
which gave highest score (or smallest loss if specified)
on the left out data. Not available if |
|
best_score_ |
Mean cross-validated score of the best_estimator For multi-metric evaluation, this is present only if This attribute is not available if |
|
best_params_ |
Parameter setting that gave the best results on the hold out data. For multi-metric evaluation, this is present only if |
|
best_index_ |
The index (of the For multi-metric evaluation, this is present only if |
|
scorer_ |
Scorer function used on the held out data to choose the best parameters for the model. For multi-metric evaluation, this attribute holds the validated
|
|
n_splits_ |
The number of cross-validation splits (folds/iterations). |
|
refit_time_ |
Seconds used for refitting the best model on the whole dataset. This is present only if |
|
multimetric_ |
Whether or not the scorers compute several metrics. |
|
classes_ |
list
|
The classes labels. This is present only if |
n_features_in_ |
list
|
Number of features seen during |
feature_names_in_ |
list
|
Names of features seen during |
Examples:
>>> from sportsbet.evaluation import BettorGridSearchCV, OddsComparisonBettor, backtest
>>> from sportsbet.dataloaders import DataLoader
>>> from sportsbet.sources import SampleSoccerOdds, SampleSoccerStats
>>> from sklearn.model_selection import TimeSeriesSplit
>>> dataloader = DataLoader(
... param_grid={'league': ['England']}, stats=SampleSoccerStats(), odds=SampleSoccerOdds()
... )
>>> X, Y, O = dataloader.extract_train_data(odds_type='market_average')
>>> bettor = BettorGridSearchCV(
... estimator=OddsComparisonBettor(),
... param_grid={'alpha': [0.02, 0.05, 0.1]},
... cv=TimeSeriesSplit(2),
... )
>>> results = backtest(bettor, X, Y, O, cv=TimeSeriesSplit(2))
>>> 'Number of bets' in results.columns
True
Source code in src/sportsbet/evaluation/_model_selection.py
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 | |
bet(X, O)
Predict the value bets for the provided input data and odds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
The input data. |
required |
O
|
DataFrame
|
The odds data. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
B |
BoolData
|
The value bets. |
Source code in src/sportsbet/evaluation/_model_selection.py
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 | |
fit(X, Y, O=None)
Fit the bettor to the input data and multi-output targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
The input data. |
required |
Y
|
DataFrame
|
The multi-output targets. |
required |
O
|
DataFrame | None
|
The odds data. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
self |
Self
|
The fitted bettor object. |
Source code in src/sportsbet/evaluation/_model_selection.py
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 | |
predict(X)
Predict class labels for multi-output targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
The input data. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Y |
BoolData
|
The positive class labels. |
Source code in src/sportsbet/evaluation/_model_selection.py
496 497 498 499 500 501 502 503 504 505 506 507 508 | |
predict_proba(X)
Predict class probabilities for multi-output targets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
DataFrame
|
The input data. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Y |
Data
|
The positive class probabilities. |
Source code in src/sportsbet/evaluation/_model_selection.py
482 483 484 485 486 487 488 489 490 491 492 493 494 | |
ClassifierBettor(classifier, betting_markets=None, init_cash=None, stake=None)
Bases: MetaEstimatorMixin, BaseBettor
Bettor based on a Scikit-Learn classifier.
Read more in the user guide.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
classifier
|
BaseEstimator
|
A scikit-learn classifier object implementing |
required |
betting_markets
|
list[str] | None
|
Select the betting markets from the ones included in the data. |
None
|
init_cash
|
float | None
|
The initial cash to use when betting. |
None
|
stake
|
float | None
|
The stake of each bet. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
classifier_ |
BaseEstimator
|
The fitted clone of |
init_cash_ |
float
|
The checked initial cash. |
Examples:
>>> from sklearn.tree import DecisionTreeClassifier
>>> from sklearn.preprocessing import OneHotEncoder
>>> from sklearn.impute import SimpleImputer
>>> from sklearn.pipeline import make_pipeline
>>> from sklearn.compose import make_column_transformer
>>> from sportsbet.evaluation import ClassifierBettor, backtest
>>> from sportsbet.dataloaders import DataLoader
>>> from sportsbet.sources import SampleSoccerOdds, SampleSoccerStats
>>> dataloader = DataLoader(
... param_grid={'league': ['England']}, stats=SampleSoccerStats(), odds=SampleSoccerOdds()
... )
>>> X, Y, O = dataloader.extract_train_data(odds_type='market_average')
>>> # Create a pipeline to handle categorical features and missing values
>>> clf_pipeline = make_pipeline(
... make_column_transformer(
... (OneHotEncoder(handle_unknown='ignore'), ['league', 'home_team', 'away_team']),
... remainder='passthrough',
... ),
... SimpleImputer(),
... DecisionTreeClassifier(random_state=0),
... )
>>> bettor = ClassifierBettor(clf_pipeline)
>>> results = backtest(bettor, X, Y, O)
>>> 'Number of bets' in results.columns
True
Source code in src/sportsbet/evaluation/_classifier.py
73 74 75 76 77 78 79 80 81 | |
OddsComparisonBettor(odds_types=None, alpha=0.05, betting_markets=None, init_cash=None, stake=None)
Bases: BaseBettor
Bettor based on comparison of odds.
It compares each market's odds to a consensus probability. The consensus probability is the average of the
selected odds types, adjusted by alpha. The method follows
Beating the bookies with their own numbers.
Read more in the user guide.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
odds_types
|
list[str] | None
|
The odds types to use for the calculation of consensus probabilities. The
default value corresponds to |
None
|
alpha
|
float
|
An adjustment term that corresponds to the difference between the consensus and real probabilities. |
0.05
|
betting_markets
|
list[str] | None
|
Select the betting markets from the ones included in the data. |
None
|
init_cash
|
float | None
|
The initial cash to use when betting. |
None
|
stake
|
float | None
|
The stake of each bet. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
odds_types_ |
list[str]
|
The checked value of the odds types. |
alpha_ |
float
|
The checked value of the alpha parameter. |
output_keys_ |
list[str]
|
The market base names of the output columns. |
Examples:
>>> from sportsbet.evaluation import OddsComparisonBettor, backtest
>>> from sportsbet.dataloaders import DataLoader
>>> from sportsbet.sources import SampleSoccerOdds, SampleSoccerStats
>>> dataloader = DataLoader(
... param_grid={'league': ['England']}, stats=SampleSoccerStats(), odds=SampleSoccerOdds()
... )
>>> X, Y, O = dataloader.extract_train_data(odds_type='market_average')
>>> bettor = OddsComparisonBettor(alpha=0.03)
>>> results = backtest(bettor, X, Y, O)
>>> 'Number of bets' in results.columns
True
Source code in src/sportsbet/evaluation/_rules.py
71 72 73 74 75 76 77 78 79 80 81 | |
backtest(bettor, X, Y, O, cv=None, n_jobs=-1, verbose=0)
Backtest the bettor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bettor
|
BaseBettor
|
The bettor object. |
required |
X
|
DataFrame
|
The input data. Each row of |
required |
Y
|
DataFrame
|
The multi-output targets. Each row of |
required |
O
|
DataFrame
|
The odds data. The column names follow the convention for the odds
data |
required |
cv
|
TimeSeriesSplit | None
|
Provides train/test indices to split time series data samples
that are observed at fixed time intervals, in train/test sets. The
default value of the parameter is |
None
|
n_jobs
|
int
|
Number of CPU cores to use when parallelizing the backtesting runs.
The default value of |
-1
|
verbose
|
int
|
The verbosity level. |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
results |
DataFrame
|
The backtesting results. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
Examples:
>>> from sklearn.model_selection import TimeSeriesSplit
>>> from sportsbet.dataloaders import DataLoader
>>> from sportsbet.sources import SampleSoccerOdds, SampleSoccerStats
>>> from sportsbet.evaluation import OddsComparisonBettor, backtest
>>> dataloader = DataLoader(
... param_grid={'league': ['England']}, stats=SampleSoccerStats(), odds=SampleSoccerOdds()
... )
>>> X, Y, O = dataloader.extract_train_data(odds_type='market_average')
>>> bettor = OddsComparisonBettor(betting_markets=['home_win', 'draw', 'away_win'])
>>> results = backtest(bettor, X, Y, O, cv=TimeSeriesSplit(2))
>>> # The folds run forward in time, so a model is never tested on a match it was trained on.
>>> list(results.index.names)
['Training start', 'Training end', 'Testing start', 'Testing end']
>>> 'Yield percentage per bet' in results.columns
True
Source code in src/sportsbet/evaluation/_model_selection.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
build_bettor(model)
Build a betting model from a scikit-learn expression or a reference to your own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
A scikit-learn estimator written as a Python expression, with the library's bettors and every
scikit-learn estimator already in scope, as in |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bettor |
BaseBettor
|
The betting model, ready to fit. |
Raises:
| Type | Description |
|---|---|
BuildError
|
When the expression or the reference does not describe a bettor. |
Source code in src/sportsbet/evaluation/_factory.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
derive_complementary_events(markets)
Return the groups of markets that are mutually exclusive and exhaustive.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
markets
|
list[str]
|
The betting markets the data carries. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
events |
list[list[str]]
|
The groups of markets whose probabilities sum to one. |
Examples:
>>> from sportsbet.evaluation import derive_complementary_events
>>> derive_complementary_events(['home_win', 'draw', 'away_win', 'over_2.5', 'under_2.5'])
[['home_win', 'draw', 'away_win'], ['over_2.5', 'under_2.5']]
>>> derive_complementary_events(['home_win', 'away_win'])
[['home_win', 'away_win']]
>>> derive_complementary_events(['draw'])
[]
Source code in src/sportsbet/evaluation/_base.py
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |
derive_market_base(market)
Return the base market name (drop the __status__time suffix).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
market
|
str
|
The market column name to reduce to its base. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The base market name (e.g. |
Source code in src/sportsbet/evaluation/_base.py
26 27 28 29 30 31 32 33 34 35 36 | |
find_latest_odds_column(columns, base, provider=None)
Return the odds column for a market base at the latest snapshot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
columns
|
list[str]
|
Candidate odds column names ( |
required |
base
|
str
|
The market base to match (e.g. |
required |
provider
|
str | None
|
If given, only match this provider. |
None
|
Returns:
| Type | Description |
|---|---|
str | None
|
The matching column at the latest |
Source code in src/sportsbet/evaluation/_base.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | |
load_bettor(path)
Load the bettor object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
The path of the bettor pickled file. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bettor |
BaseBettor
|
The bettor object. |
Examples:
>>> import tempfile
>>> from pathlib import Path
>>> from sportsbet.dataloaders import DataLoader
>>> from sportsbet.sources import SampleSoccerOdds, SampleSoccerStats
>>> from sportsbet.evaluation import OddsComparisonBettor, load_bettor, save_bettor
>>> path = str(Path(tempfile.mkdtemp()) / 'bettor.pkl')
>>> dataloader = DataLoader(
... param_grid={'league': ['England']}, stats=SampleSoccerStats(), odds=SampleSoccerOdds()
... )
>>> X, Y, O = dataloader.extract_train_data(odds_type='market_average')
>>> save_bettor(OddsComparisonBettor(betting_markets=['home_win']).fit(X, Y, O), path)
>>> bettor = load_bettor(path)
>>> # It is ready to bet without being fitted again: one row per match, one column per market.
>>> bettor.bet(X, O).shape
(380, 1)
Source code in src/sportsbet/evaluation/_base.py
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 | |
save_bettor(bettor, path)
Save the bettor object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bettor
|
BaseBettor
|
The bettor object. |
required |
path
|
str
|
The path to save the object. |
required |
Examples:
>>> import tempfile
>>> from pathlib import Path
>>> from sportsbet.dataloaders import DataLoader
>>> from sportsbet.sources import SampleSoccerOdds, SampleSoccerStats
>>> from sportsbet.evaluation import OddsComparisonBettor, load_bettor, save_bettor
>>> path = str(Path(tempfile.mkdtemp()) / 'bettor.pkl')
>>> dataloader = DataLoader(
... param_grid={'league': ['England']}, stats=SampleSoccerStats(), odds=SampleSoccerOdds()
... )
>>> X, Y, O = dataloader.extract_train_data(odds_type='market_average')
>>> bettor = OddsComparisonBettor(betting_markets=['home_win', 'draw', 'away_win']).fit(X, Y, O)
>>> save_bettor(bettor, path)
>>> # A fitted bettor comes back fitted.
>>> load_bettor(path).betting_markets_.tolist()
['home_win', 'draw', 'away_win']
Source code in src/sportsbet/evaluation/_base.py
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 | |