const ai = new Vue({ el: "#ai", data: { error: "", forecast: [], max: undefined, min: undefined, net: new brain.recurrent.LSTMTimeStep(), trained: false, training: false }, methods: { predict: (values) => { final = []; for (value of ai.net.forecast(values, 30)) { final.push(value * (ai.max - ai.min) + ai.min); }; ai.forecast = final; }, train: (data) => { values = data.map((d) => { return d.value }); trainData = []; ai.max = Math.max(...values); ai.min = Math.min(...values); for (value of values) { trainData.push( (value - ai.min) / (ai.max - ai.min) ); } if (!ai.training && !ai.trained) { ai.error = ""; ai.training = true; setTimeout( () => { ai.net.train([ trainData ], { iterations: 5 }); ai.training = false; ai.trained = true; document.getElementById("network").innerHTML = brain.utilities.toSVG( ai.net, { height: 500 } ); ai.predict(values); }, 250 ); } else { ai.predict(values); } } } }); const graph = new Vue({ el: "#graph", data: { candles: [], latest: undefined }, methods: { addCandle: (candle) => { graph.candles.push({ date: candle[0], value: candle[2] }); graph.latest = new Date(candle[0]); } }, watch: { candles: (values) => { sorted = values.slice(); sorted.sort((a, b) => { if (a.date < b.date) return -1; if (a.date > b.date) return 1; return 0; }); dates = sorted.map((d) => d.date); for (let i=0; i<30; i++) { last = dates[dates.length - 1]; dates.push(last + 60000); } ai.train(sorted); d3.select("#data").select("g").remove(); element = document.querySelector("#chart-space"); margin = { top: 10, right: 30, bottom: 100, left: 60 } height = element.clientHeight - margin.top - margin.bottom; width = element.clientWidth - margin.left - margin.right; svg = d3.select("#data") .append("g") .attr("transform", `translate(${margin.left}, ${margin.top})`); x = d3.scaleTime() .domain(d3.extent(dates, (d) => { return d })) .range([ 0, width ]); svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x)); y = d3.scaleLinear() .domain([0, d3.max(sorted, (d) => { return +d.value })]) .range([ height, 0 ]); svg.append("g") .call(d3.axisLeft(y)); regression = ss.linearRegression(sorted.map((d) => { return [+d.date, d.value] })); line = ss.linearRegressionLine(regression); trendline = x.domain().map((x) => { return { date: x, value: line(+x) } }); svg.append("path") .datum(trendline) .attr("fill", "none") .attr("stroke", "red") .attr("stroke-width", 1) .attr("d", d3.line().x((d) => { return x(d.date); }).y((d) => { return y(d.value); })); svg.append("path") .datum(sorted) .attr("fill", "none") .attr("stroke", "black") .attr("stroke-width", 2) .attr("d", d3.line().x((d) => { return x(d.date); }).y((d) => { return y(d.value); })); if (ai.forecast.length > 0) { forecast = []; position = 30; for (value of ai.forecast) { forecast.push({ date: dates[dates.length - position], value: value }); position--; } svg.append("path") .datum(forecast) .attr("fill", "none") .attr("stroke", "green") .attr("stroke-width", 1.5) .attr("d", d3.line().x((d) => { return x(d.date); }).y((d) => { return y(d.value); })); } } } }); const websocket = new Vue({ el: "#websocket", created: () => { this.socket = new WebSocket("wss://api-pub.bitfinex.com/ws/2") this.socket.onopen = () => { websocket.connected = true }; this.socket.onclose = () => { websocket.info = ""; websocket.connected = false; }; this.socket.onmessage = (message) => { json = JSON.parse(message.data); if (json.event == "info") websocket.info = `Server ${json.serverId} v${json.version}`; if (Array.isArray(json) && json[1] !== "hb") websocket.processValues(json); }; }, data: { connected: false, info: "", socket: undefined }, methods: { processCandle: (candle) => { graph.addCandle(candle); }, processValues: (json) => { if (Array.isArray(json[1][0])) { json[1].forEach((data) => websocket.processCandle(data)); } else { websocket.processCandle(json[1]); } } }, watch: { connected: (connected) => { if (connected === true) this.socket.send('{"event":"subscribe","channel":"candles","key":"trade:1m:tBTCUSD"}'); } } });