Skip to content

Instantly share code, notes, and snippets.

@amirmazmi
Last active March 10, 2025 17:21
Show Gist options
  • Save amirmazmi/a0592a2800eb069c98f48da0f3a46ccb to your computer and use it in GitHub Desktop.
Save amirmazmi/a0592a2800eb069c98f48da0f3a46ccb to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Binance XRP</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
<script src="https://cdn.plot.ly/plotly-3.0.0.min.js" charset="utf-8"></script>
</head>
<body>
<br>
<div id='group_info'>
<a id="binance_next_url"></a>
<a id="luno_next_url"></a>
<pre id="binance_raw"></pre>
<pre id="luno_raw" ><br><br></pre>
<span id="debug">...</span>
</div>
<div id="userinputs">
<div>
<label for="timer">
Update every <input type="text" id="timer" name="timer" value="60" pattern="^\d{0,4}$"
size=4 autocomplete="off"> seconds
</label>
<!--
<span>&emsp;&emsp;&emsp;</span>
<label for='debugenable'>
Debug
<input type="checkbox" id="debugenable" onclick="">
</label>
-->
</div>
<br>
<label for="fxrate">USD/MYR FX rate </label>
<input type="text" id="fxrate" name="fxrate" value="4.46" pattern="^\d{0,2}\.\d{0,2}$"
size=6 autocomplete="off">
</div>
<br>
<div id="myChart"></div>
<br><br><br>
<div id="tables">
<details>
<summary>Binance data</summary>
<div id="parsedTable"></div>
</details>
<details>
<summary>Luno data</summary>
<div id="LunoTable"></div>
</details>
</div>
</body>
<script>
luno_done = false; binance_done = false;
main();
function main() {
dateToday = new Date();
var binanceNextURL = binanceURL();
var lunoNextURL = lunoURL();
pullData(lunoNextURL);
pullData(binanceNextURL);
waitForEl( plotting);
// timer
t_repeat = parseFloat( document.getElementById("timer").value, 2);
let currtime = new Date();
let delay = 10;
sleeptime = 1_000 * ((t_repeat - currtime.getSeconds() % t_repeat) + delay) ;
console.log( currtime + " sleep: " + sleeptime);
setTimeout( () => { main() }, sleeptime);
};
function plotting() {
percdiff( plotPrice);
// plotPrice();
}
document.getElementById("fxrate").addEventListener("change", () => {
var tablediv = document.getElementById("parsedTable");
tablediv.removeChild( tablediv.firstChild);
genTableData();
waitForEl( plotting);
// console.log("[debug] input event listener ");
});
function pullData( urlNew) {
var xhttp_data = new XMLHttpRequest();
xhttp_data.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
if (this.responseText != 'None') {
data_container = document.querySelector('#' + urlNew.exchange + '_raw');
data_container.innerHTML = this.responseText;
if ( urlNew.exchange === 'binance') {
genTableData();
} else {
parseLuno();
};
};
//runAJAX( pullData(newURL), 20_000); // recursive call itself
};
};
xhttp_data.open("GET", urlNew.link, true);
xhttp_data.send();
};
function genTableData() {
var rawdata = document.getElementById('binance_raw');
var fxrate = parseFloat( document.getElementById("fxrate").value, 2);
parseddata = JSON.parse(rawdata.innerText);
var tablediv = document.getElementById("parsedTable");
if ( tablediv.children.length > 0 ) { tablediv.removeChild( tablediv.firstChild); };
document.getElementById('parsedTable').appendChild( buildHtmlTable(parseddata, fxrate) );
};
var _table_ = document.createElement('table'),
_thead_ = document.createElement('thead'),
_tbody_ = document.createElement('tbody'),
_tr_ = document.createElement('tr'),
_th_ = document.createElement('th'),
_td_ = document.createElement('td');
function buildHtmlTable(arr, forex) {
binance_x = [];
binance_y = [];
var colheaders = ['datetime','open','high','low','close','volume', 'time']
var table = _table_.cloneNode(false),
tbody = _tbody_.cloneNode(false),
columns = addAllColumnHeaders( colheaders, table);
//console.log(columns);
function addAllColumnHeaders(arr, table) {
var columnSet = [],
tr = _tr_.cloneNode(false);
thead = _thead_.cloneNode(false);
for (var i = 0, l = arr.length; i < l; i++) {
columnSet.push(i);
var th = _th_.cloneNode(false);
th.appendChild(document.createTextNode(arr[i]));
tr.appendChild(th);
thead.appendChild(tr);
};
table.appendChild(thead);
return columnSet;
};
// row
for (var i = arr.length-1, maxi = 0; i >= maxi; --i) {
var tr = _tr_.cloneNode(false); //var ls_debug = [];
// cell in row
for (var j = 0, maxj = columns.length; j < maxj; ++j) {
var td = _td_.cloneNode(false);
if( j < 1 ){
cell_data = epochToDate( arr[i][ columns[j]] );
binance_x.push(cell_data);
} else if (j > 0 && j < 5) {
cell_data = String( parseFloat( arr[i][ columns[j] ] ) * forex ).substring(0,7);
if ( j == 4) { binance_y.push( parseFloat(cell_data)); };
} else if ( j === 5) {
cell_data = parseFloat(arr[i][ columns[j] ]).toLocaleString();
} else if ( j === 6) {
cell_data = arr[i][ columns[0] ];
// binance_x.push(cell_data);
} else { cell_data = arr[i][ columns[j] ]; };
td.appendChild(document.createTextNode( cell_data || ''));
tr.appendChild(td);
//ls_debug.push(cell_data);
};
// console.log(ls_debug);
tbody.appendChild(tr);
};
table.appendChild(tbody);
binance_x.reverse();
binance_y.reverse();
binance_done = true;
return table;
};
function binanceURL() {
let timestamp = currTimestamp('Binance', dateToday);
let link = 'https://www.binance.com/api/v3/uiKlines?endTime=' + timestamp +
'&limit=100&symbol=XRPUSDT&interval=1m';
let objlink = document.getElementById('binance_next_url');
objlink.innerHTML = link;
objlink.href = link;
return { 'link': link, 'exchange': 'binance'};
};
// Luno prices
// https://ajax.luno.com/ajax/1/udf/history?symbol=XRPMYR&resolution=1&
// from=1741122338&to=1741176098&countback=896&currencyCode=XRPMYR
function lunoURL() {
let timestamp = currTimestamp('Luno', dateToday) / 1_000;
let prev_timestamp = timestamp - 8500;
let link = 'https://ajax.luno.com/ajax/1/udf/history?symbol=XRPMYR&resolution=1&from=' +
// prev_timestamp + '&to=' + timestamp + '&currencyCode=XRPMYR';
prev_timestamp + '&to=' + timestamp + '&countback=2&currencyCode=XRPMYR';
let objlink = document.getElementById('luno_next_url');
objlink.innerHTML = link;
objlink.href = link;
document.querySelector('#debug').innerHTML =
document.querySelector('#debug').innerHTML + "<br>" +
"timestamp:" + timestamp +
" prev: " + prev_timestamp +
" diff: " + (timestamp - prev_timestamp);
return { 'link': link, 'exchange': 'luno'};
};
function parseLuno() {
luno_x = [];
luno_y= [];
x_cols = JSON.parse( document.querySelector('#luno_raw').innerText).t;
for ( var m = x_cols.length-100, maxm = x_cols.length; m < maxm; ++m) {
let dd = new Date( x_cols[m] * 1_000 );
luno_x.push( epochToDate(dd));
};
luno_y = JSON.parse( document.querySelector('#luno_raw').innerText).c.slice(x_cols.length-100,x_cols.length);
luno_done = true;
document.querySelector('#debug').innerHTML =
document.querySelector('#debug').innerHTML + "<br>" + "x_cols length: " + x_cols.length;
};
function waitForEl( _callback) {
// if (document.getElementById('parsedTable').children.length > 0 &&
// document.getElementById('luno_raw').innerText.length > 0) {
if ( luno_done && binance_done ) {
console.log( "luno[" + luno_x[luno_x.length - 1] + "][" + luno_done + "] " +
"binance[" + binance_x[binance_x.length-1] + "][" + binance_done + "]"
);
_callback();
luno_done = false;
binance_done = false;
} else {
setTimeout( function() { waitForEl( _callback); }, 1_000);
}
};
// funtion lunoTable() {
// // generate headers
// let luno_colheaders = ['datetime','open','high','low','close','volume']
// tr = _tr_.cloneNode(false);
// thead = _thead_.cloneNode(false);
// for ( let w = 0, maxw = luno_colheaders.length; w < maxw; ++w ){
// th.appendChild(document.createTextNode(arr[i]));
// tr.appendChild(th);
// thead.appendChild(tr);
// };
// table.appendChild(thead);
//
// // row
// lunodata = JSON.parse( document.querySelector('#luno_raw').innerText);
// for (let y = 0, maxy = lunodata.t.length; y < maxy; ++y) {
// var tr = _tr_.cloneNode(false);
// var td = _td_.cloneNode(false);
// }
// }
function linspace(start, stop, num, endpoint = true) {
const div = endpoint ? (num - 1) : num;
const step = (stop - start) / div;
return Array.from({length: num}, (_, i) => start + step * i);
}
//https://stackoverflow.com/questions/69468126/plotly-js-overlaying-two-different-graphs-with-same-x-axis-and-different-y-axi
function plotPrice( perclabel) {
var trace_binance = { name: 'Binance', x: binance_x, y: binance_y,
mode:"lines", type: 'scatter',
line: { color: 'blue', width: 2}
};
var trace_luno = { name: 'Luno', x: luno_x, y: luno_y,
mode: "lines", type: 'scatter',
line: { color: 'red', width: 2}
};
var trace_perc = { name: perclabel + ' (%)', x: luno_x, y: ls_perc, yaxis: 'y2',
mode: "lines+markers", type: 'scatter',
line: { color: 'green', width: 0.3},
marker: { size: 4}
};
var data = [ trace_luno , trace_binance, trace_perc];
var layout = { title: { text: "XRP - Binance vs Luno", x: 0.07, y: 0.93, automargin: false },
showlegend: true,
legend: { x: 0.5, y: 1.12, xanchor: 'center', yref: 'paper', orientation: "h" },
margin: { l: 60, r: 60},
hovermode:'x unified',
modebar: {activecolor: 'green', color: 'orange', bgcolor: 'lightgrey'},
xaxis: { type:'date', tickformat: '%H:%M:%S %a', tickangle: 35,
nticks: 15, autorange: true, automargin: true},
yaxis: { title: { text: "Price (RM)"},
dtick: 0.10, tickformat: ".2f", hoverformat: ".4f",
range: [ 0.97 * Math.min(...luno_y.concat(binance_y)),
1.002 * Math.max(...luno_y.concat(binance_y))]
},
yaxis2: { title: { text: 'Diff (%)',
font: {color: 'rgb(148, 103, 189)'} },
tickfont: {color: 'rgb(148, 103, 189)'},
// dtick: 0.005,
tickformat: ".3f", hoverformat: ".2%",
overlaying: 'y', side: 'right', showgrid: false,
range: [Math.min(...ls_perc), 2.5 * Math.max(...ls_perc)]
},
plot_bgcolor: "#F2F4F4",
paper_bgcolor: "#F8F9F9",
shapes: [{ type: 'line', xref: 'paper', yref: 'y2',
x0: 0, y0: 0.01,
x1: 1, y1: 0.01,
line:{ color: 'rgb(148, 103, 189)', width: 1, dash:'dot'}
}]
};
Plotly.newPlot( "myChart", data, layout);
};
function percdiff( _callback) {
// if ( binance_y[binance_y.length -1] - luno_y[luno_y.length -1] >= 0 ){
if ( binance_y[0] - luno_y[0] >= 0 ){
fndiff = (iter) => { return String((( binance_y[iter] / luno_y[iter] ) - 1)).substring(0,6); };
difflabel = 'Bin/Luno';
} else {
fndiff = (iter) => { return String( (( luno_y[iter] / binance_y[iter] ) - 1)).substring(0,6); };
difflabel = 'Luno/Bin';
}
ls_perc = [];
for( let u = 0, maxu = luno_y.length; u < maxu; ++u) {
ls_perc.push( parseFloat( fndiff(u) ));
};
// return difflabel;
_callback(difflabel);
};
/* misc funcs */
zeroPad = function(nNum, nPad) { return ('' + (Math.pow(10, nPad) + nNum)).slice(1); };
let runAJAX = (fn, delay_timer) => setTimeout(function(){ fn();}, delay_timer);
function epochToDate(intDate) {
let strEpoch = new Date( intDate);
return [ strEpoch.getFullYear(), zeroPad(strEpoch.getMonth(),2),
zeroPad(strEpoch.getDate(),2) ].join('-') +
" " +
[strEpoch.getHours(), strEpoch.getMinutes(), strEpoch.getSeconds()]
.map((a)=>(a < 10 ? '0' + a : a))
.join(':');
};
function currTimestamp(exchange, currDate) {
function epoch (date) { return Date.parse(date)};
let timestamp = epoch(currDate);
console.log(exchange.padStart(8, " ") + " - " + dateToday);
return timestamp;
};
// Full Screen button - https://henryegloff.com/fullscreen-button/
if (document.fullscreenEnabled) {
const fullscreen_button = document.createElement("button");
fullscreen_button.setAttribute('id','fullscreen-button');
fullscreen_button.addEventListener("click", toggle_fullscreen);
fullscreen_button.innerHTML = `
<svg viewBox="0 0 24 24">
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
</svg>
<svg viewBox="0 0 24 24">
<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>
</svg>
`;
document.body.appendChild(fullscreen_button);
};
function toggle_fullscreen() {
if (!document.fullscreenElement) {
document.body.requestFullscreen();
document.body.setAttribute("fullscreen","");
} else {
document.exitFullscreen();
document.body.removeAttribute("fullscreen");
}
};
</script>
<noscript> Sorry kid, you done eff'd up!!! <br> You BLOCKED JavaScript! </noscript>
<style>
#group_info{
display: none;
}
/* Link */
a {
display: None; /* none; block; */
width: 90%;
/* height: 0%; */
white-space: pre-wrap;
word-wrap: break-word;
}
/* rawdata */
pre {
display: none; /* none; block; */
font-size: 0.75em;
width: 100%;
height: 10%;
white-space: pre-wrap;
word-wrap: break-word;
}
#debug {
display: none;
}
/* Table */
#tables {
width: 100%;
}
details {
align: left;
}
#parsedTable {
display: block;
width: 97%;
}
#parsedTable > table {
width: 100%;
font-size: 0.5em;
}
#parsedTable > table > tbody > tr:hover {
background-color: lightyellow;
font-weight: bold;
}
#parsedTable > table > thead > tr > th {
width: 35px;
}
#parsedTable > table > thead > tr > th:first-child {
width: 150px;
}
#parsedTable > table > tbody > tr > td {
text-align: center;
}
/* FX input */
#fxrate {
background-color: moccasin;
font-size: 0.85em;
height: 15px;
border: 1px solid black;
border-radius: 6px;
text-align: center;
}
label {
/* font-size: 1.15em; */
}
/* Timer input */
#timer {
text-align:center;
font-size: 0.85em;
height: 15px;
}
#userinputs {
font-size: 0.7em;
}
/* Plot */
#myChart {
width: 100%;
max-width: 900px;
height: 400px;
}
body {
display: flex;
justify-content: center;
flex-flow: column nowrap;
align-items: center;
width: 100%;
/* max-width: 850px; */
margin: auto;
}
/* LARGER SCREENS */
@media (min-width: 1250px) {
body {
max-width: 1500px;
}
#userinputs {
font-size: 1.0em;
}
#fxrate {
height: 20px;
font-size: 1.0em;
}
/*label {
font-size: 1.15em;
}*/
#timer {
height: 20px;
font-size: 1.15em;
}
#myChart {
width:100%;
max-width:1200px;
height:750px;
}
#parsedTable > table {
width: 80%;
font-size: 1em;
}
}
/* .js-plotly-plot .plotly .modebar {
left: 50%;
transform: translateX(-50%);
}*/
body, ::backdrop {
background: #fff;
}
#fullscreen-button {
position: absolute;
top: 15px;
right: 15px;
background: rgba(0,0,0,0.05);
border: 0;
width: 40px;
height: 40px;
border-radius: 50%;
box-sizing: border-box;
transition: transform .3s;
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: center;
}
#fullscreen-button:hover {
transform: scale(1.125);
}
#fullscreen-button svg:nth-child(2) {
display: none;
}
[fullscreen] #fullscreen-button svg:nth-child(1) {
display: none;
}
[fullscreen] #fullscreen-button svg:nth-child(2) {
display: inline-block;
}
</style>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment