Last active
March 10, 2025 17:21
-
-
Save amirmazmi/a0592a2800eb069c98f48da0f3a46ccb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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>   </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¤cyCode=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 + '¤cyCode=XRPMYR'; | |
prev_timestamp + '&to=' + timestamp + '&countback=2¤cyCode=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