NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name FTX Custom Theme
// @namespace http://github.com/tradersamwise/
// @version 1.1.1
// @description Custom theme for FTX
// @author @TraderSamwise
// @match https://ftx.com/*
// @icon https://www.google.com/s2/favicons?domain=tampermonkey.net
// @grant none
// @license MIT
// ==/UserScript==
const COMPACT_TABLE = true;
const SQUARE_BUTTONS = true;
const SQUARE_CARDS = true;
const DISABLE_CELL_WRAPPING = true;
const BACKGROUND_COLOR = '#587369';
const TABLE_BODY_COLOR = '#273a3a';
const TABLE_HEADER_COLOR = '#0a1f1f';
const SHOW_BTC_PNL = true;
const BTC_PNL_PRECISION = 4;
const BTC_SUFFIX = "₿";
const SHOW_PNL_PERCENT = true;
const PNL_PERCENT_PRECISION = 2;
// set specific style
function addGlobalStyle(css) {
var head, style;
head = document.getElementsByTagName('head')[0];
if (!head) {
return;
}
style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = css;
head.appendChild(style);
}
// reference btc price
let btcPrice;
// fetch the last btc price
function fetchBtcPrice() {
fetch('https://ftx.com/api/markets/BTC-PERP')
.then(response => response.json())
.then(data => {
btcPrice = data.result.price;
}).catch((error) => {
console.log(error);
});
// recheck every 10 minutes
setTimeout(fetchBtcPrice, 1000 * 60 * 10);
}
// US Number Formatter
var formatter = new Intl.NumberFormat(undefined, {
style: 'currency',
currency: 'USD',
// These options are needed to round to whole numbers if that's what you want.
//minimumFractionDigits: 0, // (this suffices for whole numbers, but will print 2500.10 as $2,500.1)
//maximumFractionDigits: 0, // (causes 2500.99 to be printed as $2,501)
});
// function to parse USD notional string value for all possible locals and formats
function parseNotionalString(val) {
val = val.replace(/\./g, '');
val = val.replace(/,/g, '');
val = val.replace(/US\$/g, '');
val = val.replace(/USD/g, '');
val = val.replace(/ /g, '');
val = val.replace(/\$/g, '');
val = val.trim()
val = val.substr(0, val.length - 2) + "." + val.substr(val.length - 2, val.length);
val = parseFloat(val);
return val;
}
// whether we are currently updating the table
let updating = false;
// converts pnl for all rows and updates the cells and the top header row
function convertPnlHelper() {
let rows = document.getElementsByClassName("MuiTableBody-root")[3].children;
let totalUsdPnl = 0;
let totalNotional = 0;
for (var i = 0; i < rows.length; i++) {
let pnlCell = rows[i].children[6].children[0];
let formattedPnl = pnlCell.innerText;
// USED FOR TESTING! DO NOT DELETE
// if (!formattedPnl.includes("|")) {
// formattedPnl = formattedPnl.replace(/\./g, '_');
// formattedPnl = formattedPnl.replace(/,/g, '.');
// formattedPnl = formattedPnl.replace(/_/g, ',');
// formattedPnl = formattedPnl.replace(/\$/g, 'US$ ');
// pnlCell.innerText = formattedPnl;
// }
let rawPnl = formattedPnl.split("|")[0];
rawPnl = parseNotionalString(rawPnl)
totalUsdPnl += rawPnl;
let percentagePnl;
if (SHOW_PNL_PERCENT) {
let formattedNotionalSize = rows[i].children[3].innerText;
let rawNotionalSize = parseNotionalString(formattedNotionalSize);
percentagePnl = (rawPnl / rawNotionalSize * 100).toFixed(PNL_PERCENT_PRECISION);
totalNotional += rawNotionalSize;
}
if (!formattedPnl.includes("|")) {
let btcPnl = (rawPnl / btcPrice);
let formattedPnlBtc = btcPnl.toFixed(BTC_PNL_PRECISION)
pnlCell.innerHTML = pnlCell.innerText + "  |  " + formattedPnlBtc + " " + BTC_SUFFIX;
if (SHOW_PNL_PERCENT) {
pnlCell.innerHTML += "  |  " + percentagePnl + '%';
}
pnlCell.style["white-space"] = "nowrap";
}
}
// if we have any open positions, add total to row header
if (rows.length > 0) {
let formattedBtcTotal = (totalUsdPnl / btcPrice).toFixed(BTC_PNL_PRECISION) + " " + BTC_SUFFIX;
let formattedUsdTotal = formatter.format(totalUsdPnl);
let pnlRowHeader = document.getElementsByClassName("MuiTableRow-head")[3].children[6];
pnlRowHeader.style["padding-top"] = "5px";
pnlRowHeader.style["padding-bottom"] = "5px";
let pnlColor = "#02C77A";
if (totalUsdPnl < 0) {
pnlColor = "#FF3B69"
}
if (SHOW_PNL_PERCENT) {
let totalPercentagePnl = (totalUsdPnl / totalNotional * 100).toFixed(PNL_PERCENT_PRECISION);
pnlRowHeader.innerHTML = "<span style=\"white-space: nowrap; font-size: 0.875rem; font-weight: 700; color: " + pnlColor + "; \"> " + formattedUsdTotal + "  |  " + formattedBtcTotal + "  |  " + totalPercentagePnl + "% </span>";
}
else {
pnlRowHeader.innerHTML = "<span style=\"white-space: nowrap; font-size: 0.875rem; font-weight: 700; color: " + pnlColor + "; \"> " + formattedUsdTotal + "  |  " + formattedBtcTotal + "</span>";
}
}
}
// iterate over the rows and convert pnl
function convertPnl() {
// only update once we have fetched btc price
if (!updating && btcPrice) {
updating = true;
const table = document.getElementsByClassName("MuiTableBody-root")[3];
table.removeEventListener("DOMSubtreeModified", convertPnl);
try {
convertPnlHelper();
}
catch (error) {
console.log(error);
}
table.addEventListener("DOMSubtreeModified", convertPnl);
updating = false;
}
}
// set the styles according to preferences
(function () {
'use strict';
COMPACT_TABLE && addGlobalStyle('.MuiTableCell-root {padding-top: 0px; padding-bottom: 0px;}');
SQUARE_BUTTONS && addGlobalStyle('.MuiButton-root {border-radius: 0px;}');
SQUARE_CARDS && addGlobalStyle('.MuiPaper-rounded {border-radius: 0px;}');
DISABLE_CELL_WRAPPING && addGlobalStyle('.MuiTableCell-body {white-space: nowrap;}');
// set background color
addGlobalStyle('.react-grid-layout {background-color: ' + BACKGROUND_COLOR + ';}');
addGlobalStyle('.jss11 {background-color: ' + BACKGROUND_COLOR + ';}');
// set table body color
addGlobalStyle('.MuiPaper-root {background-color: ' + TABLE_BODY_COLOR + ';}');
// set table header color
addGlobalStyle('.MuiAppBar-root {background-color: ' + TABLE_HEADER_COLOR + ';}');
addGlobalStyle('.jss305 {background-color: ' + TABLE_HEADER_COLOR + ';}');
// fix toastr font color
addGlobalStyle('.MuiSnackbarContent-message {color: white;}');
// show btc pnl
if (SHOW_BTC_PNL) {
fetchBtcPrice();
setTimeout(function () {
const table = document.getElementsByClassName("MuiTableBody-root")[3];
table.addEventListener("DOMSubtreeModified", convertPnl);
convertPnl();
document.getElementsByClassName("MuiButtonBase-root MuiTab-root MuiTab-textColorInherit Mui-selected MuiTab-fullWidth")[3].children[0].addEventListener("click", function () {
setTimeout(function () {
convertPnl();
}, 200)
});
}, 3000);
}
})();