Skip to content

Instantly share code, notes, and snippets.

@ColinEberhardt
Last active November 23, 2018 11:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ColinEberhardt/6e287f871410ecd970b038343b166514 to your computer and use it in GitHub Desktop.
Save ColinEberhardt/6e287f871410ecd970b038343b166514 to your computer and use it in GitHub Desktop.
Cryptocurrency average order price versus size
license: mit
<html>
<head>
<!-- include polyfills for custom event, Symbol and Custom Elements -->
<script src="//unpkg.com/babel-polyfill@6.26.0/dist/polyfill.js"></script>
<script src="//unpkg.com/custom-event-polyfill@0.3.0/custom-event-polyfill.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/document-register-element/1.8.0/document-register-element.js"></script>
<!--
use babel so that we can use arrow functions and other goodness in this block!
-->
<script src="//unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="//unpkg.com/d3@5.5.0"></script>
<script src="//unpkg.com/d3fc@14.0.22"></script>
<!-- perspective! -->
<script src="//unpkg.com/@jpmorganchase/perspective-examples@0.2.0-beta.3/build/perspective.js"></script>
<style>
body {
font-family: sans-serif;
}
#chart {
width: 100%;
height: 500px;
}
.label rect {
fill: none;
}
.point path {
opacity: 0.5;
}
.y-label {
transform: rotate(-90deg);
}
.point text,
g.label text {
font-size: 10px;
stroke: none;
fill: black;
}
.plot-area {
overflow: visible !important;
}
</style>
</head>
<body>
<div id="chart"></div>
<script src="index.js" type="text/babel"></script>
</body>
</html>
// issue - global URL
const GDAX_URL = "https://api.pro.coinbase.com/",
SOCKET_URL = "wss://ws-feed.pro.coinbase.com";
const insertIf = (condition, ...elements) => (condition ? elements : []);
const scatterConfig = {
// received orders are the only ones with size
filter: [["type", "==", "received"], ["side", "==", "buy"]],
row_pivot: ["product_id"],
column_pivot: [],
aggregate: [
{ op: "avg", column: "price" },
{ op: "avg", column: "size" },
{ op: "count", column: "order_id" }
],
sort: []
};
(async () => {
let initialised = false,
table,
scatterView,
suppress = false;
const res = await fetch(`${GDAX_URL}products`);
const products = await res.json();
const color = d3
.scaleOrdinal(d3.schemeCategory10)
.domain(products.map(d => d.base_currency));
let buffer = [];
setInterval(async () => {
// to do - raise buffer.length bug
if (initialised && buffer.length > 0 && !suppress) {
try {
table.update(buffer);
render();
} catch (e) {
console.error(e);
}
buffer = [];
}
}, 50);
const render = async () => {
const scatterCols = await scatterView.to_columns();
const series = scatterCols.__ROW_PATH__
.map((p, i) => ({
price: scatterCols.price[i],
size: scatterCols.size[i],
count: scatterCols.order_id[i],
instrument: p[0]
}))
.slice(1);
const labelPadding = 2;
const textLabel = fc
.layoutTextLabel()
.padding(labelPadding)
.value(d => d.instrument);
const labels = fc
.layoutLabel(fc.layoutGreedy())
.key(d => d.instrument)
.size((d, i, g) => {
const textSize = g[i].getElementsByTagName("text")[0].getBBox();
return [
textSize.width + labelPadding * 2,
textSize.height + labelPadding * 2
];
})
.position(d => [d.size, d.price])
.component(textLabel);
const size = d3
.scaleLinear()
.range([20, 3600])
.domain(fc.extentLinear().accessors([d => d.count])(series));
const pointSeries = fc
.seriesSvgPoint()
.key(d => d.instrument)
.crossValue(d => d.size)
.mainValue(d => d.price)
.size(d => size(d.count))
.decorate(sel => {
sel.attr("fill", d => color(d.instrument.split("-")[0]));
});
const multi = fc
.seriesSvgMulti()
.series([pointSeries, ...insertIf(suppress, labels)]);
const chart = fc
.chartCartesian(d3.scaleLog(), d3.scaleLog())
.xDomain([0.01, 10000])
.yDomain([0.001, 10000])
.xTickFormat(d3.format(","))
.yTickFormat(d3.format(","))
.xLabel("size")
.yLabel("price")
.xTickValues([0.1, 1, 10, 100, 1000])
.yTickValues([0.001, 0.1, 1, 10, 100, 1000])
.svgPlotArea(multi);
d3.select("#chart")
.datum(series)
.transition()
.duration(200)
.call(chart);
d3.select("#chart")
.on("mouseover", () => {
suppress = true;
render();
})
.on("mouseout", () => {
suppress = false;
render();
});
};
const ws = new WebSocket(SOCKET_URL);
ws.onopen = () => {
ws.send(
JSON.stringify({
type: "subscribe",
product_ids: products.map(p => p.id)
})
);
};
ws.onmessage = msg => {
if (document.hidden) {
return;
}
const data = JSON.parse(msg.data);
buffer.push(data);
if (!initialised) {
if (buffer.length > 200) {
table = perspective.worker().table(buffer, {
limit: 5000
});
initialised = true;
scatterView = table.view(scatterConfig);
render();
}
}
};
ws.onerror = console.log;
ws.onclose = e => console.log(e.code, e.reason);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment