How-to : building your own crypto trading platform with nodeJS and Websockets

Hey everyone,

In this brief post I will show you how you can easily build your own trading platform so your clients, when connecting to your server, can see real time charts and data! For the data we are going to use the Bittrex API, so start of by creating your account and verifying it so you can have your API keys.

Note : this is not to be used in production mode, it is a simple demo about how to use websockets using nodeJS. Also note that this code is bricked in some parts, so you will have to do your own reading and work if you want to implement this correctly.

We are first going to setup our web server, with the httpmodule, expressto route our pages and pathsimply to access our assets when needed :

const http = require('http')
const express = require('express')
const path = require('path')

Since I want to be sure the server is up and running before anything else, we will write an async method that will boot up a minimal server, then we can proceed with the rest of the code :

async function initialize_server() {

  try {

      const app = express()
      const router = express.Router()

      var server_instance = http.createServer(app)
      server_instance.listen(8080)

      //prepare the GET response to return index.html
      router.get('/',function(req,res){
        res.sendFile(path.join(__dirname + '/index.html'))
      });

      //add the router to the express app
      app.use('/', router)

      app.use(express.static(__dirname + '/'))
      app.use(express.static(__dirname + '/assets/'))

      //return the promise of having a server instance
      return server_instance

    } catch (err) {
      console.log(err)
    }
}

Bittrex API version 1.1 requires you to use signalr-clientto fetch real-time data . Let’s start by installing the required nodeJS packages :

PS C:\Users\ialkas\Desktop\myapp> npm install signalr-client

[...]

+ signalr-client@0.0.20
added 1 package from 1 contributor and audited 1144 packages in 4.073s
found 0 vulnerabilities

PS C:\Users\ialkas\Desktop\myapp>

At this point, you will realize that Bittrex Websocket API is quite undocumented, you will have to look up for the websocket hubs by yourself since there is no available information on their API. Lets create our instance of signalr-client :

const signalR = require('signalr-client')
const zlib = require('zlib') //unzip messages
const crypto = require('crypto') //used for authentication challenge
const client = new signalR.client('wss://socket.bittrex.com/signalr', ['c2'])

To initialize a connection with the Bittrex server, we send an GetAuthContextmessage with our API key, upon receiving, the server will send us a challenge to solve with our secret key, then we authenticate with the the signed challenge.

You can subscribe to many endpoints as documented in the Bittrex API, but here we only need to subscribe to SubscribeToExchangeDeltas, where we can fetch live data about a single market, in our case it will be BTC-ETH.

We have to setup a callback function when receiving the data, which will be in JSON format. The method we will implement will be called on_ue_receive.

client.serviceHandlers.connected = function initialize_bittrex_websocket() {

    console.log('Initializing WebSocket connection with Bittrex API...')

    //send GetAuthContext message
    client.call('c2', 'GetAuthContext', api_key).done(function(err, challenge) {
       if (err) {
               console.log(err)
       }

        //resolve challenge
        const signed_challenge = crypto.createHmac('sha512', secret_key).update(challenge).digest('hex')

        //send Authenticate message
        client.call('c2', 'Authenticate', api_key, signed_challenge).done(function(auth_err, auth_result) {
            if (auth_err) {
                console.log('auth_ERROR', auth_err)
            }
            console.log('Authentication : ', auth_result)

            //subscribe to exchanges deltas...
        client.call('c2', 'SubscribeToExchangeDeltas', 'BTC-ETH').done(function(err, result) {
                if (err) {
                    return console.error(err)
                }
                if (result === true) {
                    //set callback function
                    client.on('c2', 'uE', on_ue_receive)
                }
            })

        })
    })
}

Now you already have a webserver running and a Websocket connection to the Bittrex API. What is next is what we do when receiving a callback for the data, which is base64 encoded. The expected JSON data will be in this format :

{
  "MarketName": "string",
  "Nonce": "int",
  "Buys": [
    {
      "Type": "int",
      "Rate": "decimal",
      "Quantity": "decimal"
    }
  ],
  "Sells": [
    {
      "Type": "int",
      "Rate": "decimal",
      "Quantity": "decimal"
    }
  ],
  "Fills": [
    {
      "FillId": "int",
      "OrderType": "string",
      "Rate": "decimal",
      "Quantity": "decimal",
      "TimeStamp": "date"
    }
  ]
}

However note that your received objects will be minified JSON data, so you will have to look at the minified JSON keys in the docs. Now we implement our callback :

//callback method that is execute on message receive
function on_ue_receive(message) {

    let raw = new Buffer.from(message, 'base64')
    zlib.inflateRaw(raw, function(err, inflated) {
        if (!err) {
            let obj = JSON.parse(inflated.toString('utf8'))
            if (obj.f) {
               console.log(obj);
            } 
        } else {
            console.log(err)
        }
    })
}

Sweet! Now you are already logging all the market data we receive. Next step is to use this data and create our own candlesticks.

How this will happen is simple, since we are not going to push all the data received from the Bittrex server to our clients, we are going to create a 1 minute timer (for 1 minute candles in this case) that will broadcast the data every minute to all our users.

We will define first the data we need in a candlestick in a simple structure. How we define a candle is quite simple, we compile all the data from filled orders every time frame (1min) and we define our OHLC data.

class candlestick {

  constructor() {

    var ms = 1000 * 60 *1
    var gmt_time = 5 * 60 * 60 * 1000 // hint : I am 5 hours away from GMT

    var latest_candle_time = new Date(Math.round(new Date().getTime() / ms) * ms + gmt_time)
    this.time = latest_candle_time

    this.year = latest_candle_time.getFullYear() //yyyy
    this.month = latest_candle_time.getMonth() + 1 //range between [1-12]
    this.day = latest_candle_time.getDate() //[1-31]
    this.hour = latest_candle_time.getHours() //[0-23]
    this.minutes = latest_candle_time.getMinutes() //[0-59]

    //if all values are 0, then client shows last candle as same...
    this.o = 0
    this.h = 0
    this.l = 0
    this.c = 0
    this.volume = 0

    this.fillArray = []
  }

  create_candle() {
    var num_fills = this.fillArray.length
    if (num_fills > 0) {
      this.o = this.fillArray[0].R //first order (open)
      this.h = this.fillArray[0].R
      this.l = this.fillArray[0].R
      this.c = this.fillArray[num_fills-1].R //last order (close)
    }

    for (var i = 0; i < num_fills; i++) {
      if (this.fillArray[i].R > this.h) {
        this.h = this.fillArray[i].R
      }
      if (this.fillArray[i].R < this.l) {
        this.l = this.fillArray[i].R
      }
      this.volume += this.fillArray[i].Q
    }
  }
  add_delta(delta) {
    
    var num_fills = delta.f.length
    for (var i = 0; i < num_fills; i++) {
      if (typeof delta.f[i] == "undefined") {} else {
        this.fillArray.push(delta.f[i])

      }
    }
  }
}

Now instead of logging the JSON objects received in our on_ue_receivemethod, we will implement a method that will merge all the data and reset the candle every 60 seconds :

var current_candlestick = new candlestick()
setInterval(function() {
  current_candlestick = new candlestick() //resets the candle stick
}, 60000);

function merge_deltas(delta) {
  current_candlestick.add_delta(delta)
}

Now we have candlesticks that hold all our filled trades informations and is reset every minute. Something you should do is to store the candles in a database, so you can retrieve the information when needed.

However I will not cover this part here, you can simply code an async function that pushes the candle data in the database before resetting it.

Next up is to communicate this data to our clients! We will simply use socket.io to set up this time our own Websocket service, so install the correct package and write the code to initialize the service.

Note here that I wrote this as an async function, point is that we will to initialize this after our http server is set up and what not other service you want to be running before broadcasting data. We also log the IP of users just for the sake of it :

const socketio = require('socket.io')
async function initialize_socketio(server_instance) {

  try {

    io = socketio(server_instance, {})

    //on client connection
    io.on('connection', function(socket) {

      //fetch user IP and log this in DB
      var client_ip_address = socket.request.connection.remoteAddress
      console.log('New connection from ' + client_ip_address + "...")

      //create a callback when user is disconnected
      socket.on('disconnect', function () {
        console.log("User with ip : " + client_ip_address + " disconnected...")
      });
    })

  } catch (err) {
    console.log(err)
  }
}

Now that the clients can create a persistent connection with our server, we can broadcast our data every minute before resetting the candle.

I also suggest you to minify the data that is sent, you can also encode it in base64 so you reduce your bandwith.

This will make a huge difference when you are going to have hundreds our thousands of clients with persistent connections :

io.emit("candlestick", candle_data_minified)

At this point what is left for our server is only to initialize everything, sequentially!

initialize_server()
  .then(server_instance => {
    initialize_socketio(server_instance)
})
node myapp.js

Now that we have our server up and running, let’s code the client side html page. We are going to use PlotlyJS so we can quickly create a chart and update it with our data.

You can use react components also, but here I will stick with only PlotlyJS for simplicity. So include the required JS in your html page and create a candlestick plot.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>My charting app</title>

        <link rel="stylesheet" href="style.css">

        <script src="plotly-latest.min.js"></script>
    </head>
    <body>
        <div id="myChart"></div>
               
    </body>

    <script type="text/javascript">

       
    Plotly.d3.csv('1min_candlestick.csv', function(err, rows){

    function unpack(rows, key) {
      return rows.map(function(row) {
        return row[key];
      });
    }

    var trace = {
      x: unpack(rows, 'DateTime'),
      close: unpack(rows, 'Close'),
      high: unpack(rows, 'High'),
      low: unpack(rows, 'Low'),
      open: unpack(rows, 'Open'),
      volume:unpack(rows,'Volume'),

      increasing: {line: {color: 'green'}},
      decreasing: {line: {color: 'red'}},

      type: 'candlestick',
      xaxis: 'x',
      yaxis: 'y'
    };

    var data = [trace];

    var layout = {
      xaxis: {
        autorange: true,
        title: 'Date'
      },
      yaxis: {
        autorange: true,
          domain:[0,1],
          type:'linear'
      }
    };

      Plotly.plot('myDiv', data, layout);
    });

    </script>

</html>

As you can see here I already import some data from a CSV file. You can send a GET request to your server to fetch up to date data. Hint : use your own candlestick that you stored in a database.

What is interesting to us is to add the traces of the incoming data. It is surprisingly easy, simply initialize a socket.io and on connecting to the server, wait for incoming candlesticks and use the extendTracesmethod from PlotlyJS to push the data to your chart!

var socket = io();
socket.on('candlestick', function(msg) {

    var O = msg.o
    var H = msg.h
    var L = msg.l
    var C = msg.c

    var month = ("0" + msg.month).slice(-2);
    var day = ("0" + msg.day).slice(-2);
    var hour = ("0" + msg.hour).slice(-2);
    var minutes = ("0" + msg.minutes).slice(-2);

    var date = msg.year + "-" + month + "-" + day + " " + hour + ":" + minutes

    Plotly.extendTraces('myDiv', {
        x: [
            [date]
        ],
        close: [
            [C]
        ],
        high: [
            [H]
        ],
        low: [
            [L]
        ],
        open: [
            [O]
        ]
    }, [0]);
});

That’s it! You now have a server receiving live data from the Bittrex market, then compiling this data, pushing it to your clients every minute (or time frame you need) and finally displaying in the clients browser.

You can go further and implement a real-time Level 2 feed for example, or add custom indicators on the charts and what not. Add a couple of buttons to send coins, place orders and you have a full trading platform!

Thanks for reading!

charting demo

2 thoughts on “How-to : building your own crypto trading platform with nodeJS and Websockets”

  1. It’s an remarkable piece of writing in support of all the web users;
    they will get benefit from it I am sure.

Leave a Reply

Your email address will not be published. Required fields are marked *