Friday, 12 April 2013

Exploring the ICBIT Bitcoin Exchange API

Just two weeks ago I knew nothing about Bitcoins, and practically nothing about JavaScript.

 

But having been asked to look at the ICBIT API I now know a little about both.

 

This post won't describe Bitcoins (start here) or teach you how to program in JavaScript (maybe start here). Its just about the ICBIT api (start here) which enables you to build applications centred around the ICBIT Bitcoin Exchange.

Getting Started

Although details of this api (application programming interface) have been published on the ICBIT website, at the time of writing this, the details are incomplete. Fortunately, the bit you need to get started is OK, and it looks like this:-

<script src="https://api.icbit.se/socket.io/socket.io.js"></script>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js'></script>

<script>
$(document).ready(function () {
var conn = io.connect('https://api.icbit.se:443/icbit?AuthKey=YOUR_AUTH_KEY&UserId=YOUR_USER_ID');

conn.on('connect', function () {
alert('Connected to ICBIT API server!');

// Subscribe to orderbook updates for BUH3 futures

conn.emit('message', { op: 'subscribe', channel: 'orderbook_' + 'BUH3' });
});

conn.on('message', function (data) {

// Handle incoming data object.

if (data.op == 'private')
  alert('Incoming message: ' + data.private);

});
});
</script>


In order to get this working we need to first register on the ICBIT site, log in, and then copy/paste our personalised AuthKey and UserId details from the site into the test code, where it says: AuthKey=YOUR_AUTH_KEY&UserId=YOUR_USER_ID

Something you need to know about these details, is that they change from time to time. So if your test code stops working, return to the icbit website and check/copy/paste the details again.

EDIT: {14th Apr 13} You may have a problem successfully connecting or running sample code due to the security certificate for: https://api.icbit.se.
If you use Firefox you can type this in to the address bar (https://api.icbit.se/socket.io/socket.io.js) and create an exception....caveat lector! Not sure how you do is for IE or Chrome.

In fact here are 3 suggestions if you are thinking of exploring this api.

1. SanityCheck.html
Create a sample html file, test it so you are sure it works, and use it whenever your latest test code cannot connect to the site. This can save a bit of stress in situations where the site goes down, or your credentials have just been changed.

2. typeof
Everything in JavaScript is an object. So where you are trying to discover what members (e.g. properties & methods) an object contains, the "typeof" method will reveal all.

3. Thank god for error handlers.
One thing I hate about scripting is how moody it is. If you say the wrong thing, it tends to clam up and go silent on you! Whereas programming in a sensible language like Gambas or VB.classic, you get plenty of verbals when you do something stupid. So use Try...Catch to handle errors in JavaScript, and most of the time you will get some feedback.

Editing HTML

Don't know if its just me, but when I use one of those cleaver HTML editors (like Kompozer) I find it wants to change/correct/scramble what I've written each time I do a "save". So I'm going to suggest you use a text editor, something like gEdit (Linux) or Notepad++ (Windows). Both of these will colour key words for you, making it easier to read your code.

Sanity Check

So here is my SanityCheck.html

<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>SanityCheck</title>
</head>
<body>
<h4>ICBIT SanityCheck</h4>
<strong><p id="connect"></p></strong>
<p id="subs"></p>
<p id="read_counter"></p>
<p id="time"></p>

<script src="https://api.icbit.se/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>

    var channelid = 'orderbook_3';
    var counter = 0;    //count of message loops

    $(document).ready(function () {
    var conn = io.connect('https://api.icbit.se:443/icbit?AuthKey=##########################&UserId=xxxx');
    conn.on('connect', function () {
    document.getElementById("connect").innerHTML='*** connected ***';

    // Subscribe to whatever channel
    conn.emit('message', { op: 'subscribe', channel: channelid });
    document.getElementById("subs").innerHTML='Status: subscribed to ' + channelid;
    document.getElementById("subs").style.color="darkgreen";
    });

    // Handle incoming data object.
    //Message data arrives every second until we stop it.
    conn.on('message', function (objData) {
   
    var timeStamp = new Date(objData.orderbook.ts * 1000);
    document.getElementById("time").innerHTML=timeStamp;   
   
    counter = counter + 1;
    if (counter>5)        //loop for approx 5 seconds
        {
        conn.emit('message', { op: 'unsubscribe', channel: channelid });
        document.getElementById("subs").innerHTML='Status: un-subscribed from ' + channelid;
        document.getElementById("subs").style.color="darkred";   
        }
    });
    });
</script>
</body>
</html>


Just paste it into a blank file, edit the AuthKey & UserId with your details, then run it in your web browser.

If it works, great! Keep it in a safe place.

My second html file is going to show you what happens when you connect and subscribe to a "channel". You would expect that the message would contain just channel data as described in the api. But if you run this code:-

<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>ICBIT 3ch</title>
</head>
<body>
<h4>Steve's ICBIT Test Page</h4>
<strong><p id="connect"></p></strong>
<p id="subs"></p>
<p id="read_counter"></p>
<br>
<p id="op_update2"></p>
<br>
<p id="explore"></p>
<script src="https://api.icbit.se/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>

    var channelid = 'orderbook_BUU3';
    var firstrun = 10;
    var counter = 0;
    $(document).ready(function () {
    var conn = io.connect('https://api.icbit.se:443/icbit?AuthKey=###########&UserId=XXXX');
    conn.on('connect', function () {
    document.getElementById("connect").innerHTML='*** connected ***';

    // Subscribe to whatever channel
    conn.emit('message', { op: 'subscribe', channel: channelid });
    document.getElementById("subs").innerHTML='subscribed to ' + channelid;
    });

    conn.on('message', function (objData) {
    // Handle incoming data object.
    counter = counter + 1;
    document.getElementById("read_counter").innerHTML='Data Read Counter: ' + counter;
    if (counter>4)
        {
        conn.emit('message', { op: 'unsubscribe', channel: channelid });
        document.getElementById("subs").innerHTML='un-subscribed';   
        }

    //try to display all object members
    for(var propt in objData){          document.getElementById("explore").innerHTML=document.getElementById("explore").innerHTML + (propt + ': ' + objData[propt] + ' # ');
                }

    });
    });
</script>
</body>
</html>


...you will notice that the first 3 sections of the message concern channels; status, chat_general & user_balance.

EDIT: I've since discovered that these 3 channels are transmitted when we connect, not as a result of subscribing to an orderbook.

Data for the channel we subscribed to (orderbook_BUU3) appears in the 4th and subsequent message blocks.

The channel "chat_general" is not even mentioned in the api, so this is a perfect place to start exploring.

The output from my 2nd code example (above) reveals this:-

channel: chat_general # op: chat # type: history # history: [object Object],[object Object],[object Object],[object Object],.....

...which shows that chat_general includes 3 properties with values and another object with its own group of properties.

Chat in a room called General

In fact the data structure for channel "chat_general" looks like this:-

{
    "channel":"chat_general",
    "op":"chat",
    "type":"history",
    "history":
    [{
        "ts": <time & date stamp>,
        "room": "general",
        "username": <user>,
        "txt": <the message>,
    }, ...]
}


The history object includes the last 200 chat messages, so we can use some code like this:-


<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>ICBIT_Chat</title>
</head>
<body>
<h4>Steve's ICBIT Bitcoin Exchange Test Page - Chat</h4>
<strong><p id="connect"></p></strong>
<p id="subs"></p>
<p id="read_counter"></p>

<script src="https://api.icbit.se/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>

    var channelid = 'orderbook_3';
    var counter = 0;    //count of message loops
    var msgcount = 0;        //message count

    $(document).ready(function () {
    var conn = io.connect('https://api.icbit.se:443/icbit?AuthKey=###########################&UserId=XXXX');
    conn.on('connect', function () {
    document.getElementById("connect").innerHTML='*** connected ***';

    // Subscribe to whatever channel
    conn.emit('message', { op: 'subscribe', channel: channelid });
    document.getElementById("subs").innerHTML='Status: subscribed to ' + channelid;
    document.getElementById("subs").style.color="darkgreen";
    });

    conn.on('message', function (objData) {
    // Handle incoming data object. Message data arrives every second until we stop it.
    counter = counter + 1;
    if (counter>3)        //only 3rd & subsequent messages contain orderbook data
        {
        conn.emit('message', { op: 'unsubscribe', channel: channelid });
        document.getElementById("subs").innerHTML='Status: un-subscribed from ' + channelid;
        document.getElementById("subs").style.color="darkred";   
        }

    try{
        msgcount=0;
        for(var dataset in objData.history)       
            {
            msgcount=msgcount + 1;
            var dt = new Date(objData.history[msgcount-1].ts);   
            var TimeStamp=dt.toString();
            var rowX=document.getElementById('chatTable').insertRow(-1);  //build a table
            var cell0=rowX.insertCell(0);
            var cell1=rowX.insertCell(1);
            var cell2=rowX.insertCell(2);
            var cell3=rowX.insertCell(3);
            var chatter=objData.history[msgcount-1].username;
            //colour code special users
            if (chatter=="admin")
                {
                rowX.style.color="red"
                }
            else if (chatter=="elvis")
                {
                rowX.style.color="blue"
                }
            else
                {
                rowX.style.color="black"
                }
            cell0.innerHTML=msgcount + "/" + objData.history.length;
            cell1.innerHTML=TimeStamp.slice(0,24); //trim time & date
            cell2.innerHTML=chatter;
            cell3.innerHTML=objData.history[msgcount-1].txt;
            }
        }

catch(err)
  {
  txt="There was an error on this page.\n\n";
  txt+="Error description: " + err.message + "\n\n";
  txt+="Click OK to continue.\n\n";
  alert(txt);
  }
    });
    });
</script>
<table id="chatTable" border="1" width="100%">
    <tr>
    <td></td>
    <td></td>
    <td></td>
  </tr>
</table>
<br>
</body>
</html>


...to build an html page dedicated to chat.

EDIT: the example above works, but we don't need to subscribe to a channel.

Probably not very useful, but hopefully a good example of what we need to do in order to explore the other channels available via this api.

Bitcoin Orderbook

So one more example for this post. Looking at the Bitcoin exchange channel "orderbook_3" I've discovered the structure looks like this:-
{
    "channel":"orderbook",
    "op":"private",
    "origin":"broadcast",
    "private":"orderbook",
    "orderbook":
    [{
        "s": ,
        "m": ,
        "ts": 

So using some (very) rough test code like this:-


<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>ICBIT OB3</title>
</head>
<body>
<h4>Steve's ICBIT Bitcoin Exchange Test Page</h4>
<strong><p id="connect"></p></strong>
<p id="subs"></p>
<p id="read_counter"></p>
<p id="data1"></p>
<p id="data2"></p>
<p id="data3"></p>

<script src="https://api.icbit.se/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>
//    var channelid = 'orderbook_BUU3';
    //var channelid = 'orderbook_BUJ3'
    var channelid = 'orderbook_3';

    var counter = 0;    //count of message loops
    var Spropcount = 0;        //the sell property counter
    var Bpropcount = 0;        //the buy property counter
    var sellcount = 0;        //the sell dataset counter (sets of q, p, t)
    var buycount = 0;        //the buy dataset counter (sets of q, p, t)

    $(document).ready(function () {
    var conn = io.connect('https://api.icbit.se:443/icbit?AuthKey=################&UserId=XXX');
    conn.on('connect', function () {
    document.getElementById("connect").innerHTML='*** connected ***';

    // Subscribe to whatever channel
    conn.emit('message', { op: 'subscribe', channel: channelid });
    document.getElementById("subs").innerHTML='Status: subscribed to ' + channelid;
    document.getElementById("subs").style.color="darkgreen";
    });

    conn.on('message', function (objData) {
    // Handle incoming data object. Message data arrives every second until we stop it.
    counter = counter + 1;
    if (counter>3)        //only 3rd & subsequent messages contain orderbook data
        {
        conn.emit('message', { op: 'unsubscribe', channel: channelid });
        document.getElementById("subs").innerHTML='Status: un-subscribed from ' + channelid;
        document.getElementById("subs").style.color="darkred";   
        }
    document.getElementById("read_counter").innerHTML='Data Read Counter: ' + counter;
    var data6 = typeof objData.orderbook.s;        //what is "s"???
    var data7 = objData.orderbook.s;
    document.getElementById("data1").innerHTML='member s: ' + data6 + ', value: ' + data7;
    var data8 = typeof objData.orderbook.m;
    var data9 = objData.orderbook.m;            //what is "m"???
    document.getElementById("data2").innerHTML='member m: ' + data8 + ', value: ' + data9;
    var data10 = objData.orderbook.ts;
    var dt = new Date(data10 * 1000);
    document.getElementById("data3").innerHTML='member ts: ' + data8 + ', value: ' + data10 + " (" + dt + ")";
    sellcount=0;
    Spropcount=0;

    try{
    //Sell Table
    for(var dataset in objData.orderbook.sell)           
        {
        sellcount=sellcount + 1;
        for(var Spropt in objData.orderbook.sell[sellcount-1])
            {
            var rowX=document.getElementById('sellTable').insertRow(-1);  //build a table
            var cell0=rowX.insertCell(0);
            var cell1=rowX.insertCell(1);
            var cell2=rowX.insertCell(2);
            cell0.innerHTML='sell: set' + sellcount + ", property" + Spropcount;
            cell1.innerHTML=Spropt;
            cell2.innerHTML=objData.orderbook.sell[sellcount-1][Spropt];
            Spropcount = Spropcount + 1;
            }
        Spropcount=0;
        }
       
    //Buy table   
    buycount=0;
    Bpropcount=0;
    for(var dataset in objData.orderbook.buy)           
        {
        buycount=buycount + 1;
        for(var Bpropt in objData.orderbook.buy[buycount-1])
            {
            var rowY=document.getElementById('buyTable').insertRow(-1);
            var cellB0=rowY.insertCell(0);
            var cellB1=rowY.insertCell(1);
            var cellB2=rowY.insertCell(2);
            cellB0.innerHTML='buy: set' + buycount + ", property" + Bpropcount;
            cellB1.innerHTML=Bpropt;
            cellB2.innerHTML=objData.orderbook.buy[buycount-1][Bpropt];
            Bpropcount = Bpropcount + 1;
            }
        Bpropcount=0;
        }
    document.getElementById("sellTable").style.color="darkgreen";
    document.getElementById("buyTable").style.color="darkblue";
    }

catch(err)
  {
  txt="There was an error on this page.\n\n";
  txt+="Error description: " + err.message + "\n\n";
  txt+="Click OK to continue.\n\n";
  alert(txt);
  }
    });
    });
</script>
<table id="sellTable" border="1" fontcolor="green" width="50%">
    <tr>
    <td></td>
    <td></td>
    <td></td>
  </tr>
</table>
<br>
<table id="buyTable" border="1" width="50%">
  <tr>
    <td></td>
    <td></td>
    <td></td>
  </tr>
</table>
</body>
</html>


...I get output like this:-


So I can see that ts is timestamp, I've assumed "p" is sell/buy price, and "q" is sell/buy quantity.

But what is "s", "m"?

2 comments:

  1. Thanks alot for writing this :) ICBIT offical guide ain't that good, yours is much more usefull

    ReplyDelete
  2. I over heard someone yesterday saying that my code is broken. Yep, the last example is not capturing/displaying the required data.

    Can't remember exactly how this is supposed to work, but if you experiment with the line:-
    if (counter>3)
    ...changing to (say):-
    if (counter>12)
    ...you will see data as per my example.

    However, this post was only supposed to be a starting point for anyone interested in exploring the icbit api.

    At the time (early 2013) I couldn't find enough accurate api documentation to make a complete application, and was hoping that others would chip in and fill the gaps. Maybe the author has left the documentation vague for good reason, or maybe he/she is just so bright that they can't see what the problem is for dumbos like me!

    Either way, I haven't taken this any further.

    I did build a simple MxGox app but there were a couple of problems;
    1. The app ran quite well but the server would drop my connection from time to time. This was often when something exciting was happening (e.g. a sudden rise in trading volumes). So I think it was due to a lack of server capacity.
    2. I had to white-list "https://socketio.mtgox.com/socket.io/socket.io.js" in Firefox because of a security certificate problem....very worrying!

    ReplyDelete