NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name create midpoint price // @namespace https://www.betfair.com/exchange // @version 1.3 // @description Create the midpoint price // @author angelo.ndira@gmail.com // @match https://www.betfair.com/exchange/plus/*/market/* // @grant none // @require http://code.jquery.com/jquery-1.12.4.min.js // @license Apache-2.0 // ==/UserScript== var repeatExecutionInterval = 10000; var pageReloadInterval = 480000; var smallBolt = "<svg class=\"octicon octicon-zap\" viewBox=\"0 0 10 16\" version=\"1.1\" width=\"10\" height=\"16\" aria-hidden=\"true\"><path fill-rule=\"evenodd\" d=\"M10 7H6l3-7-9 9h4l-3 7z\"></path></svg>"; var tickdata = { 1000.00: 10.00, 999.99: 10.00, 100.00: 10.00, 99.99: 5.00, 50.00: 5.00, 49.99: 2.00, 30.00: 2.00, 29.99: 1.00, 20.00: 1.00, 19.99: 0.50, 10.00: 0.50, 9.99: 0.20, 6.00: 0.20, 5.99: 0.10, 4.00: 0.10, 3.99: 0.05, 3.00: 0.05, 2.99: 0.02, 2.00: 0.02, 1.99: 0.01, 0.00: 0.01 }; function ticksizeRound(unrounded) { 'use strict'; var unroundedFloat = parseFloat(unrounded); if (unroundedFloat > 1000) { return 1000; } if (unroundedFloat < 1.01) { return 1.01; } var ticksize; $.each(tickdata, function (key, value) { if (unroundedFloat < key) { ticksize = value; } }); if (typeof (ticksize) === 'undefined') { return unrounded; } return Math.round(unroundedFloat / ticksize) * ticksize; } function readPriceAndSize(rowNode, marketDataSnapshot) { 'use strict'; var selection = {}; selection.pnl = 0; rowNode.find('td.bet-buttons.back-cell.last-back-cell > button.back.mv-bet-button.back-button.back-selection-button').each(function () { var buttonNode = $(this); selection.backPrice = parseFloat(buttonNode.attr("price")); selection.backSize = parseFloat(buttonNode.attr("size").replace(",", "").substring(1)); }); rowNode.find('td.bet-buttons.lay-cell.first-lay-cell > button.lay.mv-bet-button.lay-button.lay-selection-button').each(function () { var buttonNode = $(this); selection.layPrice = parseFloat(buttonNode.attr("price")); selection.laySize = parseFloat(buttonNode.attr("size").replace(",", "").substring(1)); }); rowNode.find('div.runner-elem-pnl.actual-pnl > span.pnl-value-container > span.pnl-value.negative').each(function () { var spanNode = $(this); selection.pnl = parseFloat("-" + spanNode.text().replace(",", "").substring(2)); }); rowNode.find('div.runner-elem-pnl.actual-pnl > span.pnl-value-container > span.pnl-value.positive').each(function () { var spanNode = $(this); selection.pnl = parseFloat(spanNode.text().replace(",", "").substring(1)); }); if (typeof (selection.backPrice) === 'undefined') { return; } if (typeof (selection.backSize) === 'undefined') { return; } if (typeof (selection.layPrice) === 'undefined') { return; } if (typeof (selection.laySize) === 'undefined') { return; } if (selection.backSize === 0) { selection.backPrice = 1.01; selection.backSize = 100; } if (selection.laySize === 0) { selection.layPrice = 1000; selection.laySize = 100; } selection.backValue = +(selection.backPrice * selection.backSize).toFixed(6); selection.layValue = +(selection.layPrice * selection.laySize).toFixed(6); selection.totalValue = +(selection.backValue + selection.layValue).toFixed(6); selection.layPriceDiff = +(selection.layPrice - selection.backPrice).toFixed(6); selection.layValuePercent = +(selection.layValue / selection.totalValue).toFixed(6); selection.midPrice = +(ticksizeRound(selection.layPrice - (selection.layValuePercent * selection.layPriceDiff))).toFixed(6); selection.midSize = +((selection.totalValue / 2) / selection.midPrice).toFixed(0); selection.probability = +(1 / selection.midPrice).toFixed(6); rowNode.find('td.bet-buttons.back-cell.last-back-cell').each(function () { var tdNode = $(this); selection.selectionId = parseInt(tdNode.attr("bet-selection-id")); }); marketDataSnapshot.totalProbability += selection.probability; marketDataSnapshot.runners.push(selection); console.log(JSON.stringify(selection, null, 4)); } function readMarketDataSnapshot(rowNode, marketDataSnapshot) { 'use strict'; readPriceAndSize(rowNode, marketDataSnapshot); return marketDataSnapshot; } function insertOrReplaceHtml(newHtml, rowNode, jquerySelector, jquerySiblingSelector) { 'use strict'; var done = false; rowNode.find(jquerySelector).each(function () { if (done) { return; } var tdNode = $(this); tdNode.replaceWith(newHtml); done = true; }); rowNode.find(jquerySiblingSelector).each(function () { if (done) { return; } var tdNode = $(this); tdNode.after(newHtml); done = true; }); } function createMidPriceHtml(selectedRunner, iconMarker) { 'use strict'; var newHtml = "" + "<td id=\"charlieparkerstraat_midpoint\" class=\"bet-buttons\" bet-selection-id=\"" + selectedRunner.selectionId + "\">" + "<button class=\"mv-bet-button\" price=\"" + selectedRunner.adjustedMidPrice + "\" size=\"" + selectedRunner.midSize + "\">" + "<div class=\"mv-bet-button-info\">"; if (selectedRunner.adjustedMidPrice < 1.2) { newHtml += "<span class=\"bet-button-price\">" + selectedRunner.adjustedMidPrice + " " + iconMarker + "</span>"; } else { newHtml += "<span class=\"bet-button-price\">" + selectedRunner.adjustedMidPrice + "</span>"; } newHtml += "<span class=\"bet-button-size\">" + selectedRunner.midSize + "</span>" + "</div>" + "</button>" + "</td>"; return newHtml; } function createMidPriceColumn(rowNode, marketDataSnapshot) { 'use strict'; var selectionId; var selectedRunner; rowNode.find('td.bet-buttons.back-cell.last-back-cell').each(function () { var tdNode = $(this); selectionId = parseInt(tdNode.attr("bet-selection-id")); }); $.each(marketDataSnapshot.runners, function (index, runner) { if (runner.selectionId === selectionId) { selectedRunner = runner; } }); var newHtml = createMidPriceHtml(selectedRunner, smallBolt); var jquerySelector = 'td#charlieparkerstraat_midpoint'; var jquerySiblingSelector = 'td.bet-buttons.back-cell.last-back-cell'; insertOrReplaceHtml(newHtml, rowNode, jquerySelector, jquerySiblingSelector); return marketDataSnapshot; } function adjustProbability(marketDataSnapshot) { 'use strict'; $.each(marketDataSnapshot.runners, function (index, runner) { runner.adjustedProbability = +(runner.probability / marketDataSnapshot.totalProbability).toFixed(6); runner.adjustedMidPrice = +(ticksizeRound(1 / runner.adjustedProbability)).toFixed(2); }); } function enforceLowerBound(lowerBound, number) { 'use strict'; if (number < lowerBound) { return lowerBound; } return number; } function display(newHtml, jquerySelector, jquerySiblingSelector, runner) { 'use strict'; var done = false; $(document).find('table.mv-runner-list > tbody > tr.runner-line').each(function () { var rowNode = $(this); var selectionId; rowNode.find('td.bet-buttons.back-cell.last-back-cell').each(function () { var tdNode = $(this); selectionId = parseInt(tdNode.attr("bet-selection-id")); }); if (done) { return; } if (runner.selectionId === selectionId) { insertOrReplaceHtml(newHtml, rowNode, jquerySelector, jquerySiblingSelector); done = true; } }); } function spacePad(num, places) { var spaces = places - num.toString().length + 1; return Array(+(spaces > 0 && spaces)).join(" ") + num; } function getCashoutByLaying(fraction, smallestProfit, impliedPrice, impliedSize) { var theoCashoutProfit = +(fraction * smallestProfit).toFixed(6); var theoSize = +(impliedSize + theoCashoutProfit).toFixed(6); var cashoutLayPrice = +(ticksizeRound((impliedSize / theoSize) * impliedPrice)).toFixed(2); var cashoutLaySize = +((impliedPrice / cashoutLayPrice) * impliedSize).toFixed(2); var cashoutLaySizeAdjusted = +(enforceLowerBound(2, cashoutLaySize)).toFixed(6); if (cashoutLaySizeAdjusted !== cashoutLaySize) { cashoutLayPrice = +(ticksizeRound((impliedSize / cashoutLaySizeAdjusted) * impliedPrice)).toFixed(2); cashoutLaySize = cashoutLaySizeAdjusted; } var cashoutByLaying = {}; cashoutByLaying.layPrice = cashoutLayPrice; cashoutByLaying.laySize = cashoutLaySize; return cashoutByLaying; } function getCashoutByBacking(fraction, smallestProfit, impliedPrice, impliedSize) { var theoCashoutProfit = +(fraction * smallestProfit).toFixed(6); var theoSize = +(enforceLowerBound(2, smallestProfit - theoCashoutProfit)).toFixed(6); var cashoutBackPrice = +(ticksizeRound((impliedSize / theoSize) * impliedPrice)).toFixed(2); var cashoutBackSize = +((impliedPrice / cashoutBackPrice) * impliedSize).toFixed(2); var cashoutBackSizeAdjusted = +(enforceLowerBound(2, cashoutBackSize)).toFixed(6); if (cashoutBackSizeAdjusted !== cashoutBackSize) { cashoutBackPrice = +(ticksizeRound((impliedSize / cashoutBackSizeAdjusted) * impliedPrice)).toFixed(2); cashoutBackSize = cashoutBackSizeAdjusted; } var cashoutByBacking = {}; cashoutByBacking.backPrice = cashoutBackPrice; cashoutByBacking.backSize = cashoutBackSize; return cashoutByBacking; } function calcAndDisplayLayCashout(largestLiabilityRunner, smallestProfitRunner) { 'use strict'; var largestLiability = largestLiabilityRunner.pnl; var smallestProfit = smallestProfitRunner.pnl; var impliedPrice = +((smallestProfit / Math.abs(largestLiability)) + 1).toFixed(6); var impliedSize = Math.abs(largestLiability); var cashout000 = getCashoutByLaying(0.0, smallestProfit, impliedPrice, impliedSize); var cashout030 = getCashoutByLaying(0.3, smallestProfit, impliedPrice, impliedSize); var cashout060 = getCashoutByLaying(0.6, smallestProfit, impliedPrice, impliedSize); var cashout100 = getCashoutByLaying(1.0, smallestProfit, impliedPrice, impliedSize); var newHtml = "" + "<div id=\"cashout-local-suggestions-030-060-100\">" + "<br>" + "<br>" + "<div><pre>New Bet " + spacePad("Price", 7) + " X " + spacePad("Size", 7) + "</pre></div>" + "<div><pre>Lay 0% " + spacePad((cashout000.layPrice).toFixed(2), 7) + " X " + spacePad((cashout000.laySize).toFixed(2), 7) + "</pre></div>" + "<div><pre>Lay 30% " + spacePad((cashout030.layPrice).toFixed(2), 7) + " X " + spacePad((cashout030.laySize).toFixed(2), 7) + "</pre></div>" + "<div><pre>Lay 60% " + spacePad((cashout060.layPrice).toFixed(2), 7) + " X " + spacePad((cashout060.laySize).toFixed(2), 7) + "</pre></div>" + "<div><pre>Lay 100% " + spacePad((cashout100.layPrice).toFixed(2), 7) + " X " + spacePad((cashout100.laySize).toFixed(2), 7) + "</pre></div>" + "</div>"; var jquerySelector = 'div#cashout-local-suggestions-030-060-100'; var jquerySiblingSelector = 'td.new-runner-info > div.runner-info-container > div.pnl.runner-info-elem'; display(newHtml, jquerySelector, jquerySiblingSelector, smallestProfitRunner); } function calcAndDisplayBackCashout(largestLiabilityRunner, smallestProfitRunner) { 'use strict'; var largestLiability = largestLiabilityRunner.pnl; var smallestProfit = smallestProfitRunner.pnl; var impliedBackPrice = +((smallestProfit / Math.abs(largestLiability)) + 1).toFixed(6); var impliedPrice = +((1 / (impliedBackPrice - 1)) + 1).toFixed(6); var impliedSize = smallestProfit; var cashout000 = getCashoutByBacking(0.0, smallestProfit, impliedPrice, impliedSize); var cashout030 = getCashoutByBacking(0.3, smallestProfit, impliedPrice, impliedSize); var cashout060 = getCashoutByBacking(0.6, smallestProfit, impliedPrice, impliedSize); var cashout100 = getCashoutByBacking(1.0, smallestProfit, impliedPrice, impliedSize); var newHtml = "" + "<div id=\"cashout-local-suggestions-030-060-100\">" + "<br>" + "<br>" + "<div><pre>New Bet " + spacePad("Price", 7) + " X " + spacePad("Size", 7) + "</pre></div>" + "<div><pre>Back 0% " + spacePad((cashout000.backPrice).toFixed(2), 7) + " X " + spacePad((cashout000.backSize).toFixed(2), 7) + "</pre></div>" + "<div><pre>Back 30% " + spacePad((cashout030.backPrice).toFixed(2), 7) + " X " + spacePad((cashout030.backSize).toFixed(2), 7) + "</pre></div>" + "<div><pre>Back 60% " + spacePad((cashout060.backPrice).toFixed(2), 7) + " X " + spacePad((cashout060.backSize).toFixed(2), 7) + "</pre></div>" + "<div><pre>Back 100% " + spacePad((cashout100.backPrice).toFixed(2), 7) + " X " + spacePad((cashout100.backSize).toFixed(2), 7) + "</pre></div>" + "</div>"; var jquerySelector = 'div#cashout-local-suggestions-030-060-100'; var jquerySiblingSelector = 'td.new-runner-info > div.runner-info-container > div.pnl.runner-info-elem'; display(newHtml, jquerySelector, jquerySiblingSelector, largestLiabilityRunner); } function getLargestLiabilityRunner(marketDataSnapshot) { 'use strict'; var largestLiabilityRunner; var largestLiability = 0; $.each(marketDataSnapshot.runners, function (index, runner) { if (runner.pnl < 0 && runner.pnl < largestLiability) { largestLiability = runner.pnl; largestLiabilityRunner = runner; } }); return largestLiabilityRunner; } function getSmallestProfitRunner(marketDataSnapshot) { 'use strict'; var smallestProfitRunner; var smallestProfit = 0; $.each(marketDataSnapshot.runners, function (index, runner) { if (runner.pnl > 0 && runner.pnl > smallestProfit) { smallestProfit = runner.pnl; smallestProfitRunner = runner; } }); return smallestProfitRunner; } function suggestLocalCashout(marketDataSnapshot) { 'use strict'; var largestLiabilityRunner = getLargestLiabilityRunner(marketDataSnapshot); var smallestProfitRunner = getSmallestProfitRunner(marketDataSnapshot); if (typeof (largestLiabilityRunner) === 'undefined') { return; } if (typeof (smallestProfitRunner) === 'undefined') { return; } calcAndDisplayLayCashout(largestLiabilityRunner, smallestProfitRunner); calcAndDisplayBackCashout(largestLiabilityRunner, smallestProfitRunner); } function adjustHeadingPosition() { 'use strict'; $(document).find('tr.rh-line.without-lay > th.rh-select-all-buttons.rh-select-back-all-button > button.rh-back-all').each(function () { var buttonNode = $(this); try { buttonNode.css('marginRight', '91px'); } catch (ex) { console.log("error occurred while adjusting heading position. " + ex); } }); } function addMidPriceColumn(marketDataSnapshot) { 'use strict'; $(document).find('table.mv-runner-list > tbody > tr.runner-line').each(function () { var rowNode = $(this); createMidPriceColumn(rowNode, marketDataSnapshot); }); } function repeatedExecution(elapsedTimeMillis) { 'use strict'; if (elapsedTimeMillis > pageReloadInterval) { window.location.reload(false); return; } var marketDataSnapshot = {}; marketDataSnapshot.runners = []; marketDataSnapshot.totalProbability = 0; $(document).find('table.mv-runner-list > tbody > tr.runner-line').each(function () { var rowNode = $(this); readMarketDataSnapshot(rowNode, marketDataSnapshot); }); adjustProbability(marketDataSnapshot); //console.log(JSON.stringify(marketDataSnapshot, null, 4)); adjustHeadingPosition(); addMidPriceColumn(marketDataSnapshot); suggestLocalCashout(marketDataSnapshot); elapsedTimeMillis += repeatExecutionInterval; setTimeout(repeatedExecution, repeatExecutionInterval, elapsedTimeMillis); } function start() { 'use strict'; var elapsedTimeMillis = 0; setTimeout(repeatedExecution, 0, elapsedTimeMillis); } (function () { 'use strict'; console.log("document loaded(). Midpoint creation will start in 10 seconds"); setTimeout(start, 10000); })();