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);
})();