Code Monkey home page Code Monkey logo

bet's Introduction

bet

Sports betting website crawler that looks for sure bets profit opportunities and sends them to a Telegram channel.

Tech

  • TypeScript
  • Node.js
  • Puppeteer
  • Docker
  • Kubernetes

Notes

This was built for educational purposes only. Use it with caution.

bet's People

Contributors

luccamordente avatar gcangussu avatar ricardonacif avatar dependabot-preview[bot] avatar gbvaz avatar

Stargazers

wis.tec.br avatar Lucas Moreira avatar franz101 avatar

Watchers

James Cloos avatar  avatar  avatar

bet's Issues

Fix over/under operator to consider integer/integer bets

Opportunities like:

  • game score total over 1
  • game score total under 1

do not make sense since both will lose if the result is 1.

To fix this, when any value is an integer, we need to make sure that the under value is larger than the over value and vice versa.

Also, we want to make sure that the difference between the values is equal 0.5 to avoid too many improbable combinations.

0.25 differences are not acceptable because part of the invested amount can be returned to the user.

Examples:

  • ✅ over 1.5 | under 1.5
  • ✅ over 1 | under 1.5
  • ❌ over 1 | under 1.25 (over is integer and abs(over-under) != 0.5 )
  • ❌ over 1 | under 1.75 (over is integer and abs(over-under) != 0.5 )
  • ❌ over 1 | under 2 (over is integer and abs(over-under) > 0.5 )
  • ❌ over 1 | under 1 (over is integer and over == under)
  • ❌ over 1 | under 0.75 (over is integer and under < over)

Marathon scraper is crashing

(node:35) UnhandledPromiseRejectionWarning: Error: Page crashed!
    at Page._onTargetCrashed (/app/.yarn/unplugged/puppeteer-npm-2.1.1-01d5fbb78f/node_modules/puppeteer/lib/Page.js:213:24)
    at CDPSession.<anonymous> (/app/.yarn/unplugged/puppeteer-npm-2.1.1-01d5fbb78f/node_modules/puppeteer/lib/Page.js:122:56)
    at CDPSession.emit (events.js:315:20)
    at CDPSession.EventEmitter.emit (domain.js:485:12)
    at CDPSession._onMessage (/app/.yarn/unplugged/puppeteer-npm-2.1.1-01d5fbb78f/node_modules/puppeteer/lib/Connection.js:200:12)
    at Connection._onMessage (/app/.yarn/unplugged/puppeteer-npm-2.1.1-01d5fbb78f/node_modules/puppeteer/lib/Connection.js:112:17)
    at WebSocket.<anonymous> (/app/.yarn/unplugged/puppeteer-npm-2.1.1-01d5fbb78f/node_modules/puppeteer/lib/WebSocketTransport.js:44:24)
    at WebSocket.onMessage (/app/.yarn/cache/ws-npm-6.2.1-bbe0ef9859-2.zip/node_modules/ws/lib/event-target.js:120:16)
    at WebSocket.emit (events.js:315:20)
    at WebSocket.EventEmitter.emit (domain.js:485:12)
(node:35) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:35) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Pinnacle odds are different when signed in

In order to get the most accurate odds from Pinnacle one must be signed in.

To do that, all that's required is that a session token is sent with the API requests to the proper host.

The host for signed in requests is
api.arcadia.pinnacle.com
instead of
guest.api.arcadia.pinnacle.com

The session token needs to be sent as a header like this:

X-Session: SESSION_TOKEN_HERE

One needs to go over the sign in process in order to get the session token. The token is available in the browser's local storage once logged in.

Stake for bets are inverted

This is incorrect:

🍀 1.87%
🏦 Pinnacle + Marathon
🛒 Handicap de gols no jogo

________________________

🏦 Pinnacle
  🛒 game score_handicap: away -1
  ⚖️ Odd: 2.33
  💰Stake: 56.3%
  🔗 https://www.pinnacle.com/pt/soccer/a/b/1116752525/

🏦 Marathon
  🛒 game score_handicap: home 1
  ⚖️ Odd: 1.81
  💰Stake: 43.7%
  🔗 https://www.marathonbet.com/pt/betting/Football/Friendlies/Clubs/Vaster+vs+Virgo+1909+-+9264976

________________________

🎭 IF Vaster × IK Virgo
🗓  26/Mar 07:00

The correct stake is the inverse:

🏦 Pinnacle
  ⚖️ Odd: 2.33
  💰Stake: 43.7%

🏦 Marathon
  ⚖️ Odd: 1.81
  💰Stake: 56.3%

Opportunity went to the database but was not published

{
	"_id" : "5e7a25d913e05cba2c3c9576/5e7b2ffda814e993d9b9b7ca",
	"stakeables" : [
		{
			"stake" : 0.4640371229698375,
			"_id" : ObjectId("5e7b2ffda814e993d9b9b7ca"),
			"odd" : 2,
			"market" : {
				"key" : "game_score_handicap",
				"type" : "spread",
				"operation" : {
					"operator" : "home",
					"value" : -1
				}
			},
			"house" : "pinnacle",
			"sport" : "soccer",
			"event" : {
				"league" : "Club Friendlies",
				"starts_at" : ISODate("2020-03-25T18:00:00Z"),
				"participants" : {
					"home" : "Oskarshamns Aik",
					"away" : "Almeboda/Linneryd"
				}
			},
			"extracted_at" : ISODate("2020-03-25T17:24:44.712Z"),
			"url" : "https://www.pinnacle.com/pt/soccer/a/b/1116546389/"
		},
		{
			"stake" : 0.5359628770301623,
			"_id" : ObjectId("5e7a25d913e05cba2c3c9576"),
			"odd" : 2.31,
			"market" : {
				"key" : "game_score_handicap",
				"type" : "spread",
				"operation" : {
					"operator" : "away",
					"value" : 1
				}
			},
			"house" : "marathon",
			"sport" : "soccer",
			"event" : {
				"league" : null,
				"starts_at" : ISODate("2020-03-25T18:00:00Z"),
				"participants" : {
					"home" : "Oskarshamns AIK",
					"away" : "Almeboda / Linneryd"
				}
			},
			"extracted_at" : ISODate("2020-03-25T17:25:15.123Z"),
			"url" : "https://www.marathonbet.com/pt/betting/Football/Friendlies/Clubs/Oskarshamns+AIK+vs+Almeboda%252FLinneryd+-+9259882"
		}
	],
	"profit" : 0.07192575406032464,
	"createdAt" : ISODate("2020-03-25T17:12:17.500Z"),
	"updatedAt" : ISODate("2020-03-25T17:25:15.388Z")
}

Error in Marathon market comparison

marathon_1    | 💾 Marathon soccer game_score_handicap (home -3.5 ⇢ 6.95)  25/03 06:00
marathon_1    | (node:17) UnhandledPromiseRejectionWarning: Error: Member 'Almeboda/Linneryd' in price.sn 'Almeboda/Linneryd (+3.5)' doesn't match any of the extracted members in {"home":"Oskarshamns AIK","away":"Almeboda / Linneryd"}
marathon_1    |     at normalizeMarket (/usr/app/src/houses/marathon/index.ts:130:15)
marathon_1    |     at normalizeBet (/usr/app/src/houses/marathon/index.ts:150:13)
marathon_1    |     at retriveBetsAndUpdateDb (/usr/app/src/houses/marathon/index.ts:171:22)
marathon_1    |     at runMicrotasks (<anonymous>)
marathon_1    |     at processTicksAndRejections (internal/process/task_queues.js:97:5)
marathon_1    |     at Manager.run (/usr/app/src/index.ts:23:19)
marathon_1    |     at Manager.start (/usr/app/src/index.ts:15:5)
marathon_1    | (node:17) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
marathon_1    | (node:17) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

We do a check of the team name in order to map it to home or away. Sometimes the team names are different in the market title. This case it's lacking some spaces in the original

Handle events which start time are too granular

Some events get a very granular start time.

One example from Pinnacle:

💾 Pinnacle soccer game_score_handicap (home -0.5 ⇢ 1.86) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (away 0.5 ⇢ 1.97) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (home 0.5 ⇢ 1.2) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (away -0.5 ⇢ 4.36) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (home 0 ⇢ 1.34) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (away 0 ⇢ 3.22) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (home -0.25 ⇢ 1.6) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (away 0.25 ⇢ 2.35) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (home -0.75 ⇢ 2.16) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (away 0.75 ⇢ 1.7) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (home -1 ⇢ 2.78) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (away 1 ⇢ 1.44) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (home -1.5 ⇢ 3.73) 29/03 01:01
💾 Pinnacle soccer game_score_handicap (away 1.5 ⇢ 1.26) 29/03 01:01

It could be a good idea to round the times to the nearest quarter before comparing

Filter out closed markets on Pinnacle

The straight endpoint response has the status property. Sometimes it's "closed". We should be careful to only read things which status is "open".

Add support for Marathon pagination

Results from Marathon are more often than not paginated. This is the case for the events list and for the bets list.

Currently, we only get the first page for those requests. We need to get all the pages available.

Properly handle timezone on Marathon scraper

Currently, the timezone for scraping is one that could be affected by Daylight Savings Time. So at least twice a year the dates scraped could have 1h difference and would hinder the bettables uncomparable.

We need to find a way to always get UTC datetimes.

Convert other market mentions in Telegram message to use the humanized version

🍀 1.12%
🏦 Pinnacle + Marathon
🛒 Handicap de gols no jogo

________________________

🏦 Pinnacle
  🛒 game score_handicap: away 0
  ⚖️ Odd: 3.24
  💰Stake: 31.2%
  🔗 https://www.pinnacle.com/pt/hockey/a/b/1116680455/

🏦 Marathon
  🛒 game score_handicap: home 0
  ⚖️ Odd: 1.47
  💰Stake: 68.8%
  🔗 https://www.marathonbet.com/pt/betting/Ice+Hockey/Belarus/Extraleague/Play-Offs/Final/Yunost-Minsk+vs+Shakhtyor+Soligorsk+-+9263620

________________________

🎭 Yunost Minsk × Shakhtar Soligorsk
🗓  27/Mar 05:00

These:

  🛒 game score_handicap: away 0
  🛒 game score_handicap: home 0

didn't get the conversion.

Make sure to convert home/away to the team name

Marathon Scrapper died with connection error

Error getting data Protocol error (Runtime.callFunctionOn): Target closed. Error: Protocol error (Runtime.callFunctionOn): Target closed.
    at /usr/app/node_modules/puppeteer/lib/Connection.js:183:56
    at new Promise (<anonymous>)
    at CDPSession.send (/usr/app/node_modules/puppeteer/lib/Connection.js:182:12)
    at ExecutionContext._evaluateInternal (/usr/app/node_modules/puppeteer/lib/ExecutionContext.js:107:44)
    at ExecutionContext.evaluate (/usr/app/node_modules/puppeteer/lib/ExecutionContext.js:48:23)
    at ExecutionContext.<anonymous> (/usr/app/node_modules/puppeteer/lib/helper.js:112:23)
    at ElementHandle.evaluate (/usr/app/node_modules/puppeteer/lib/JSHandle.js:54:42)
    at ElementHandle.<anonymous> (/usr/app/node_modules/puppeteer/lib/helper.js:112:23)
    at FootballMatchPage.parseMembers (/usr/app/src/houses/marathon/pages/footballMatchPage.ts:71:32)
    at runMicrotasks (<anonymous>)
  -- ASYNC --
    at ExecutionContext.<anonymous> (/usr/app/node_modules/puppeteer/lib/helper.js:111:15)
    at ElementHandle.evaluate (/usr/app/node_modules/puppeteer/lib/JSHandle.js:54:42)
    at ElementHandle.<anonymous> (/usr/app/node_modules/puppeteer/lib/helper.js:112:23)
    at FootballMatchPage.parseMembers (/usr/app/src/houses/marathon/pages/footballMatchPage.ts:71:32)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at FootballMatchPage.parseContent (/usr/app/src/houses/marathon/pages/footballMatchPage.ts:103:21)
    at FootballMatchPage.getData (/usr/app/src/houses/marathon/pages/basePage.ts:49:14)
    at MarathonWebsite.retrieveBets (/usr/app/src/houses/marathon/index.ts:55:24)
    at retriveBetsAndUpdateDb (/usr/app/src/houses/marathon/index.ts:171:20)
  -- ASYNC --
    at ElementHandle.<anonymous> (/usr/app/node_modules/puppeteer/lib/helper.js:111:15)
    at FootballMatchPage.parseMembers (/usr/app/src/houses/marathon/pages/footballMatchPage.ts:71:32)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at FootballMatchPage.parseContent (/usr/app/src/houses/marathon/pages/footballMatchPage.ts:103:21)
    at FootballMatchPage.getData (/usr/app/src/houses/marathon/pages/basePage.ts:49:14)
    at MarathonWebsite.retrieveBets (/usr/app/src/houses/marathon/index.ts:55:24)
    at retriveBetsAndUpdateDb (/usr/app/src/houses/marathon/index.ts:171:20)
    at Manager.run (/usr/app/src/index.ts:23:19)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.