Home

AlgoTrader edited this page Aug 28, 2013 · 34 revisions
Clone this wiki locally

Betfair Sports API for Node.js Tutorial

Important Notice

Betfair is about to switch to a next generation JSON based API. It is certainly good news as JSON is a native JavaScript format. Let's say goodbye to SOAP, XML generation/parsing, huge network traffic and many complexities of the old SOAP API. The current project is production-ready, but I would not recommend use it for new projects. The development of the betfair-sport-api is stopped. There is a new library for the new API, although it is not usable yet.

This tutorial intends to show the reader how to create the high-end Betfair applications in fast and easy manner. Many people who were trying to make a first Betfair bot faced the steep learning curve and it may took weeks or months to do simple things like logging to Betfair and placing a bet. I do hope it will take hours to make your first bot using Betfair Sports API for Node.js with the help of the Tutorial. There are following steps in the tutorial:

Step 1. Installing Node.js and Betfair Sports API
Step 2. Logging in to Betfair
Step 3. Login to Betfair, Send keepAlive and Logout
Step 4. Login to Betfair, Send keepAlive and Logout Done Right
Step 5. Getting All the Available Markets
Step 6. Reading the Market Details
Step 7. Getting the Market Prices
Step 8. Placing and Canceling Bets for the Market
Step 9. Updating Bets for the Market
Step 10. High-Performance Bot Programming

Betfair Sports API is a simple, yet powerful JavaScript library for the rapid development of the Betfair applications or bots. It is a result of many years experience of developing the Betfair bots using different programming languages and frameworks. There is a need for a tool that allows fast and easy development of the Betfair bots still without performance and latency compromises. While the Betfair Sports API is quite friendly for beginners it is certainly the high-performance tool intended for professionals.

Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

There are many benefits of using Betfair Sports API for Node.js:

* Extreme performance, 100 calls/sec is no problem, 1000 calls/sec achievable
* Development in JavaScript is easy and rapid, it is faster than C++, C# or Java
* JavaScript V8 Engine is fast, it is much faster than most of the other scipting languages
* Fully asuncronous, placing inplay bet does not stops other markets to be refreshed
* Cross-platform: you can develop on Windows, deploy on Linux and present on MacOSX
* Hosting friendly, place the apps on VPS in UK and get fast ping
* Low memory requirements, 512Mb VPS is enough for comfortable bot operation

This is not a JavaScript or Node.js tutorial, but very minimal knowledge of JavaScript and no prior knowledge of Node.js is required.

## Step 1. Installing Node.js and Betfair Sports API.

Node.js works good on Windows, MacOSX and Linux. It is the Windows version used in the tutorial, but feel free using any other platform you like. To install Node.js on Windows, please visit the www.nodejs.org, go to the Download page and download the Windows Installer file. (at the moment of tutorial creation it is the http://nodejs.org/dist/v0.6.15/node-v0.6.15.msi file).

After downloading the MSI installer file, double click it (there might be a confirmation screen asking to confirm installer run, make sure there is Joyent publisher that signed the installer file). The first screen of the Installer Wizard is a welcome screen, just press Next button. Next screen asks you to accept the End User License Agreement, just do it. The following screen copies files into "*C:\Program Files\nodejs*" and then the Finish screen appears. That's all, there is nothing special, just the standard Windows application installation.

Node.js is now installed. There is some 8Mb of space on your hard drive used and there are two new things appear on you PC:

1. Node.exe - JavaScript V8 engine and Node.js framework in a single and compact executable file.
2. Npm - Node.js package manager, it allows to install thousands of Node.js packages on your PC

The interesting things are just starting. Go to the Windows Command Prompt (on Windows 7 just press Start button then just type cmd in search field). Let's create folder C:\Betfair and go there:

c:\Users\AlgoTrader>md c:\Betfair
c:\Users\AlgoTrader>cd c:\Betfair
c:\Betfair>

The rest of tutorial never installs anything outside the C:\Betfair directory, so you will be able just remove the directory to keep your PC clean. It is too much of installs, let's start using it. Type node from inside of C:\Betfair. To quit node, just press Ctrl-C twice.

c:\Betfair>node
> 2+3
5
> 2*Math.PI*10
62.83185307179586
> console.log('Hello, world')
Hello, world
undefined
>

It works! This is just an interactive JavaScript interpreter built-in in Node.js. The first two commands are pure JavaScript code, the last one is the Node.js console object that has log method. Now switch to another friend, npm that stands for Node Package Manager.

c:\Betfair>npm install betfair-sports-api
npm http GET https://registry.npmjs.org/betfair-sports-api
npm http 200 https://registry.npmjs.org/betfair-sports-api
npm http GET https://registry.npmjs.org/betfair-sports-api/-/betfair-sports-api-0.1.5.tgz
npm http 200 https://registry.npmjs.org/betfair-sports-api/-/betfair-sports-api-0.1.5.tgz
npm http GET https://registry.npmjs.org/async
npm http GET https://registry.npmjs.org/dom-js
npm http 200 https://registry.npmjs.org/dom-js
npm http GET https://registry.npmjs.org/dom-js/-/dom-js-0.0.7.tgz
npm http 200 https://registry.npmjs.org/async
npm http GET https://registry.npmjs.org/async/-/async-0.1.18.tgz
npm http 200 https://registry.npmjs.org/dom-js/-/dom-js-0.0.7.tgz
npm http 200 https://registry.npmjs.org/async/-/async-0.1.18.tgz
npm http GET https://registry.npmjs.org/sax
npm http 200 https://registry.npmjs.org/sax
npm http GET https://registry.npmjs.org/sax/-/sax-0.4.0.tgz
npm http 200 https://registry.npmjs.org/sax/-/sax-0.4.0.tgz
betfair-sports-api@0.1.5 ./node_modules/betfair-sports-api
├── async@0.1.18
└── dom-js@0.0.7 (sax@0.4.0)

Great, Betfair Sports API for Node is installed. There were two dependent modules installed: async and dom-js. The first one is JavaScript control-flow library, more about it later. The second is XML parser needed to parse XMLs from Betfair and encode XMLs about to send to Betfair. It's out of our interest.

## Step 2. Logging in to Betfair.

Betfair uses SOAP protocol for accessing Betfair Sports API, SOAP stands for Simple Object Access Protocol. Despite it's name, the SOAP is not that simple. SOAP uses XML for requests and responses and a number of rules of how those XML requests/responses are encoded. Betfair API uses HTTPS transport, it is encrypted version of the HTTP that is needed to protect your personal data from being stolen. There is an example of the login SOAP request:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http:
   //www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/">
  <soap:Body>
    <login xmlns="http://www.betfair.com/publicapi/v3/BFGlobalService/">
      <request>
        <locationId>0</locationId>
        <password>password</password>
        <productId>82</productId>
        <username>nobody</username>
        <vendorSoftwareId>0</vendorSoftwareId>
      </request>
    </login>
  </soap:Body>
</soap:Envelope>

Is that easy? Betfair provides three SOAP endpoints (URLs). The first is used primarily for the login/logout operations, and named Global Service. The second and third are UK and Australia exchanges and named UK Exchange and Australian Exchange. The URLs of the endpoints are following:

https://api.betfair.com/global/v3/BFGlobalService
https://api.betfair.com/exchange/v5/BFExchangeService
https://api-au.betfair.com/exchange/v5/BFExchangeService

In this tutorial we will use only the first two. When using Betfair Sports API for Node, you should not worry about the endpoints, the API knows where to send the specific API call (invocation). Now is the time to start coding, create file login.js and copy-paste the following code:

var betfair = require('betfair-sports-api');

var login='me';
var password='pass';

var session = betfair.newSession(login,password);
session.open(function(err,res) {
    if(err)
       console.log('login failed, error is', err);
    else 
       console.log('login OK');
   process.exit(1);
});

Please note that you need to be in *C:\Betfair* and betfair sports API should be installed previously by running npm install betfair-sports-api. There is a very important moment, session.open() method takes a function as the first argument. The function is defined inside the session.open() call, that is very common thing in JavaScript that may embarrass programmers that never saw such things. Now the time to run it:

C:\Betfair>node login.js
login response: raw length=459, xml length=941, compression=51%
login failed, error is INVALID_USERNAME_OR_PASSWORD

The login attempt failed as we provided invalid Betfair login and password. Please note there is some additional information printed, it would be probably removed in future versions of the library. Please edit the code and put there your Betfair login and password and try again.

C:\Betfair>node login.js
login response: raw length=481, xml length=982, compression=51%
Session token is: [skipped]
login OK

Congratulations, login to Betfair was successful! The library does not show the session token for security reasons. The security token is a magic string that looks like 'oIa2RCtt6AH5KAegdPWZYzcSUTiaQohWf1gdybjFPGE=' that API sends to Betfair in every API call to confirm that you are logged in. The only exception is the login invocation, in login request there should be your Betfair password provided. I strongly recommend you to use logout call to invalidate the session token, if someone knows you active security token then he may access your money.

## Step 3. Login to Betfair, Send keepAlive and Logout.

This step seems to be too simple and not much necessary but it is. If we used PHP, Python or C# there would not a need in the Step 3. Node.js is different in a single thing and the thing is of extreme importance. Node.js is fully asynchronous. It means that program never stops to wait anything, it never blocks on networks operations or file reads from filesystem (there are a few exceptions, but the issue out of scope of this tutorial). The first and naive approach is ther (copy-paste the text to naive.js)

var betfair = require('betfair-sports-api');

var bflogin='me';
var bfpassword='pass';

console.log('login to Betfair');
var session = betfair.newSession(bflogin,bfpassword);
session.open(function(err,res) {
    if(err)
        console.log('login failed, error is', err);
    else 
        console.log('login OK');
});

console.log('send keepAlive');
var invocation = session.keepAlive();
invocation.execute(function(err,res) {
    if(err)
        console.log('keepAlive failed, error is', err);
    else 
           console.log('keepAlive OK');
});

console.log('logout');
session.close(function(err,res) {
    if(err)
        console.log('logout failed, error is', err);
    else 
        console.log('logout OK');
    process.exit(1);
});

The result is funny:

C:\Betfair>node naive.js
login to Betfair
send keepAlive
logout
keepAlive response: raw length=404, xml length=812, compression=50%
keepAlive failed, error is NO_SESSION
logout response: raw length=411, xml length=828, compression=50%
logout failed, error is NO_SESSION

First thing is there is no any stuff related to login. The second is the keepAlive is failed with error NO_SESSION and logout also failed with error NO_SESSION. What's up? The answer is easy: all the login, keepAlive and logout were sent at once. keepAlive and logout were finished before login did. That is certainly incorrect code. Your results may be different from mine, the code may provide strange surprises. We need to call login, keepAlive and logout in series! Here is the modified and correct version (copy paste to fixed.js):

var betfair = require('betfair-sports-api');

var bflogin = 'me';
var bfpassword = 'pass';

console.log('login to Betfair');
var session = betfair.newSession(bflogin, bfpassword);
session.open(function(err, res) {
    if (err)
        console.log('login failed, error is', err);
    else {
        console.log('login OK, send keepAlive');
        var invocation = session.keepAlive();
        invocation.execute(function(err, res) {
            if (err)
                console.log('keepAlive failed, error is', err);
            else {
                console.log('keepAlive OK, logout');
                session.close(function(err, res) {
                    if (err)
                        console.log('logout failed, error is', err);
                    else
                        console.log('logout OK');
                    process.exit(1);
                });
            }
        });
    }
});

The execution result is correct now:

C:\Betfair>node fixed.js
login to Betfair
login response: raw length=478, xml length=982, compression=51%
Session token is: [skipped]
login OK, send keepAlive
keepAlive response: raw length=445, xml length=858, compression=48%
keepAlive OK, logout
logout response: raw length=393, xml length=813, compression=52%
logout OK

The code above works good, but is the code easy to read and understand? It is one of the most shit code I ever saw. The correctly written code can be the complete shit! The code provided above has own name and is called "callback hell code". It is the style of coding that use many web developers and it is coding style that cause many people to hate JavaScript. I also hated JavaScript. JavaScript is one of the most used programming languages in the world and it is also most misunderstood language as there are lots of arrogant web developers who cannot write simple things. We got six levels of indenting doing a very simple thing.

Forget about everything we did in Step 3. There is a survival of the "callback hell" and the async stuff though, let's fix the things up.

## Step 4. Login to Betfair, Send keepAlive and Logout done Right

A JavaScript application can be either very ugly or very beautiful one. JavaScript is a beautiful language when used properly. JavaScript impacted my coding style very much and Node.js also made me rethink many things. I would say it requires think different.

There is a library for Node.js (it can work without Node also) that is designed to address the "callback hell" problem, its name is async. The library has lots of useful stuff but we need just a single function from there - series. The only purpose of the function is just do things serially. Lets rewrite the code from Step 3 using async (copy-paste to serial.js and type npm install async to install async module):

var betfair = require('betfair-sports-api');
var async = require('async');

var bflogin = 'me';
var bfpassword = 'pass';
var session;

async.series([ login, keepAlive, logout ], function(err, res) {
    process.exit(0);
});

function login(callback) {
    console.log('login to Betfair');
    session = betfair.newSession(bflogin, bfpassword);
    session.open(function(err, res) {
        if (err)
            console.log('login failed, error is', err);
        else
            console.log('login OK');
        callback(err,res);
    });
}

function keepAlive(callback) {
    console.log('send keepAlive');
    var invocation = session.keepAlive();
    invocation.execute(function(err, res) {
        if (err)
            console.log('keepAlive failed, error is', err);
        else
            console.log('keepAlive OK');
        callback(err,res);
    });
}

function logout(callback) {
    console.log('logout');
    session.close(function(err, res) {
        if (err)
            console.log('logout failed, error is', err);
        else
            console.log('logout OK');
        callback(err,res);
    });
}

The invocation result is

C:\Betfair>node serial.js
login to Betfair
login response: raw length=483, xml length=982, compression=51%
Session token is: [skipped]
login OK
send keepAlive
keepAlive response: raw length=450, xml length=858, compression=48%
keepAlive OK
logout
logout response: raw length=394, xml length=813, compression=52%
logout OK

Although the code is a bit more lengthy, it is no longer difficult to understand. For the rest of the tutorial, I will use async library with async.series() to "linearize" the serial execution logic. If you think that async stuff is just a headache, think of the several cases when it help a lot. Supposedly you want to place an in-play bet and it will hang for 5 secs (BF "freeze" HTTP by 5 secs then send reply), if the application is synchronous it is either stopped to wait placeBets result or it has a number of threads and syncronization stuff (mutexes, events, semaphores, etc) that is also not so simple. The asynchronous logic helps to say goodbuy to threads, [dead]locks, races etc.

I recommend adding debug prints to the code to see what res object is, you may be especially interested in what res.result is. Let's add console.log("res.result is", res.result); in login callback:


    session = betfair.newSession(bflogin, bfpassword);
    session.open(function(err, res) {
        if (err)
            console.log('login failed, error is', err);
        else
            console.log('login OK');
        console.log("res.result is", res.result);
        callback(err, res);
    });

The output will have following lines:

res.result is { header: 
   { errorCode: 'OK',
     minorErrorCode: null,
     sessionToken: 'cRtIrpt1ekmzhy1sUHVxqpgUlRsAJZkXmZkz0RzoTcI=',
     timestamp: Sun, 06 May 2012 07:06:34 GMT },
  currency: 'USD',
  errorCode: 'OK',
  minorErrorCode: null,
  validUntil: Mon, 01 Jan 1 00:00:00 GMT }

It is a parsed XML response from Betfair, but instead of the ugly SOAP XML it is the easy to use JavaScript object. For example, to check what the sessionToken is you may use console.log(res.result.header.sessionToken). Should be easy, isn't it? The last comment is the sessionToken is modified in order to keep my money safe.

## Step 5. Getting All the Available Markets

To make any bets we should select a market first. It can be a soccer match, a horse race or anything else. For this tutorial we will use tennis. There is a getAllMarkets invocation that does exactly we need. Let's get all tennis markets that currently exists at the Betfair:

var betfair = require('betfair-sports-api');
var async = require('async');

var bflogin = 'me';
var bfpassword = 'pass';
var session;

async.series([ login, getAllMarkets, logout ], function(err, res) {
    process.exit(0);
});

function login(callback) {
    console.log('login to Betfair');
    session = betfair.newSession(bflogin, bfpassword);
    session.open(function(err, res) {
        if (err)
            console.log('login failed, error is', err);
        else
            console.log('login OK');
        callback(err, res);
    });
}

function getAllMarkets(cb) {
    console.log('Get available tennis matches');

    // eventTypeIds 1-soccer, 2-tennis
    var inv = session.getAllMarkets({
        eventTypeIds : [ 2 ]
    });
    inv.execute(function(err, res) {
        console.log('action:', res.action, 'error:', err, 'duration:', res
                .duration() / 1000);
        if (err) {
            cb("Error in getAllMarkets", null);
        }
        for ( var index in res.result.marketData) {
            market = res.result.marketData[index];
            if (market.marketName != 'Match Odds')
                continue;
            var path = market.menuPath.replace(/\\Tennis\\Group A\\/g, '')
            console.log(path);
        }
        cb(null, "OK");
    });
}

function logout(callback) {
    console.log('logout');
    session.close(function(err, res) {
        if (err)
            console.log('logout failed, error is', err);
        else
            console.log('logout OK');
        callback(err, res);
    });
}

Most of the code was inherited from the previous step. We just replaced the keepAlive function with the getAllMarkets function. Let's look at the execution results:

C:\Betfair>node getAllMarkets.js
login to Betfair
login response: raw length=481, xml length=982, compression=51%
Session token is: [skipped]
login OK
Get available tennis matches
getAllMarkets response: raw length=8975, xml length=108120, compression=92%
action: getAllMarkets error: null duration: 0.156
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Azarenka v Kuznetsova
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Petrova v King
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\A Radwanska v Arruabarrena Vecino
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Scheepers v Errani
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Wickmayer v Gajdosova
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Vinci v Cibulkova
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Peer v Hercog
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Pironkova v Medina Garrigues
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Kirilenko v Zheng
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Makarova v Voskoboeva
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Erakovic v Kvitova
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Vesnina v S Williams
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Jankovic v Suarez Navarro
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Safarova v Kanepi
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Begu v Sharapova
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Wozniacki v Pervak
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\O Rochus v Wawrinka
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Melzer v F Lopez
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Simon v Fognini
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Baghdatis v Garcia Lopez
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Raonic v Nalbandian
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Troicki v Young
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Bellucci v Gasquet
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Granollers v Berlocq
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Stepanek v Tomic
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Montanes v Cilic
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Youzhny v Marti
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\F Mayer v Del Potro
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Dolgopolov v Andujar
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Llodra v Seppi
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Anderson v Bogomolov Jr
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Kohlschreiber v Monfils
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Verdasco v Istomin
Mutua Madrid Open 2012\Mens Tournament\First Round Matches\Karlovic v Davydenko
Mutua Madrid Open 2012\Mens Tournament\Qualifying Matches\Gil v Gimeno Traver
Mutua Madrid Open 2012\Mens Tournament\Qualifying Matches\Cipolla v Hanescu
Mutua Madrid Open 2012\Mens Tournament\Qualifying Matches\Sanjurjo Hermida v Delbonis
BMW Open 2012\The Final\Cilic v Kohlschreiber
BMW Open 2012\Doubles Matches\Cermak/Polasek v Malisse/Norman
Mutua Madrid Open 2012\Mens Tournament\Qualifying Matches\Giraldo v Munoz de la Nava
Mutua Madrid Open 2012\Mens Tournament\Qualifying Matches\Stakhovsky v Roger Vasselin
Mutua Madrid Open 2012\Mens Tournament\Qualifying Matches\Andreev v Bautista Agut
\Tennis\Mutua Madrid Open 2012\Womens Tournament\Second Round Matches\Stosur v McHale
Estoril Open 2012\Mens Tournament\The Final\Del Potro v Gasquet
Serbia Open 2012\The Final\Paire v Seppi
Mutua Madrid Open 2012\Mens Tournament\Qualifying Matches\Falla v Cervantes
Estoril Open 2012\Doubles Matches\Qureshi/Rojer v Knowle/Marrero
Mutua Madrid Open 2012\Womens Tournament\Second Round Matches\Soler Espinosa v Li
Serbia Open 2012\Doubles Matches\Erlich/A Ram v Emmrich/Siljestrom
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Kerber v Larsson
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Niculescu v Dominguez Lino
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Johansson v Ivanovic
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Pavlyuchenkova v Craybas
Mutua Madrid Open 2012\Womens Tournament\First Round Matches\Schiavone v Lepchenko
logout
logout response: raw length=394, xml length=813, compression=52%
logout OK

It works great. Plese make a note the time getAllMarket taken, it is 0.156 seconds. It is the pretty bad result, but alas, I have a poor internet connection at home. There is regex expression used in the code, to cut some stuff from menuPath that is out of interest.

# Step 6. Reading the Market Details

var betfair = require('betfair-sports-api');
var async = require('async');

var bflogin = 'me';
var bfpassword = 'pass';
var session;
var marketId;

async.series([ login, getAllMarkets, getMarket, logout ], function(err, res) {
    process.exit(0);
});

function login(callback) {
    console.log('login to Betfair');
    session = betfair.newSession(bflogin, bfpassword);
    session.open(function(err, res) {
        if (err)
            console.log('login failed, error is', err);
        else
            console.log('login OK');
        callback(err, res);
    });
}

function getAllMarkets(cb) {
    console.log('Get available tennis matches');

    // eventTypeIds 1-soccer, 2-tennis
    var inv = session.getAllMarkets({
        eventTypeIds : [ 2 ]
    });
    inv.execute(function(err, res) {
        console.log('action:', res.action, 'error:', err, 'duration:',
                res.duration() / 1000);
        if (err) {
            cb("Error in getAllMarkets", null);
        }
        var markets = res.result.marketData.filter(function(m) {
            return m.marketName === 'Match Odds'
        });
        // sort by eventDate descending
        markets.sort(function(first, second) {
            return second.eventDate - first.eventDate
        });
        var market = markets[0];
        marketId = market.marketId;
        console.log('Selected market %s', marketId);
        cb(null, "OK");
    });
}

function getMarket(cb) {
    console.log('Call getMarket for marketId="%s"', marketId);
    var inv = session.getMarket(marketId);
    inv.execute(function(err, res) {
        console.log('action:', res.action, 'error:', err, 'duration:',
                res.duration() / 1000);
        if (err) {
            cb("Error in getMarket", null);
        }
        // console.log( util.inspect(res.result, false, 10) );
        console.log("marketId:", res.result.market.marketId);
        console.log("market name:", res.result.market.name);
        console.log("market time:", res.result.market.marketTime);
        console.log("\tplayerOneId:", res.result.market.runners[0].selectionId);
        console.log("\tplayerOneName:", res.result.market.runners[0].name);
        console.log("\tplayerTwoId:", res.result.market.runners[1].selectionId);
        console.log("\tplayerTwoName:", res.result.market.runners[1].name);
        cb(null, "OK");
    });
}

function logout(callback) {
    console.log('logout');
    session.close(function(err, res) {
        if (err)
            console.log('logout failed, error is', err);
        else
            console.log('logout OK');
        callback(err, res);
    });
}

The execution result is

C:\Betfair>node getMarket.js
login to Betfair
login response: raw length=479, xml length=982, compression=51%
Session token is: [skipped]
login OK
Get available tennis matches
getAllMarkets response: raw length=7633, xml length=95545, compression=92%
action: getAllMarkets error: null duration: 0.188
Selected market 105672838
Call getMarket for marketId="105672838"
getMarket response: raw length=1819, xml length=4769, compression=62%
action: getMarket error: null duration: 0.125
marketId: 105672838
market name: Match Odds
market time: Wed, 09 May 2012 09:00:00 GMT
        playerOneId: 2256470
        playerOneName: Tomas Berdych
        playerTwoId: 2519549
        playerTwoName: Kevin Anderson
logout
logout response: raw length=395, xml length=813, compression=51%
logout OK

It works

# Step 7. Getting the Market Prices

var betfair = require('betfair-sports-api');
var async = require('async');

var bflogin = 'me';
var bfpassword = 'pass';
var session;
var marketId;

async.series([ login, getAllMarkets, getMarketPrices, logout ], function(err, res) {
    process.exit(0);
});

function login(callback) {
    console.log('login to Betfair');
    session = betfair.newSession(bflogin, bfpassword);
    session.open(function(err, res) {
        if (err)
            console.log('login failed, error is', err);
        else
            console.log('login OK');
        callback(err, res);
    });
}

function getAllMarkets(cb) {
    console.log('Get available tennis matches');

    // eventTypeIds 1-soccer, 2-tennis
    var inv = session.getAllMarkets({
        eventTypeIds : [ 2 ]
    });
    inv.execute(function(err, res) {
        console.log('action:', res.action, 'error:', err, 'duration:',
                res.duration() / 1000);
        if (err) {
            cb("Error in getAllMarkets", null);
        }
        var markets = res.result.marketData.filter(function(m) {
            return m.marketName === 'Match Odds'
        });
        // sort by eventDate descending
        markets.sort(function(first, second) {
            return second.eventDate - first.eventDate
        });
        var market = markets[0];
        marketId = market.marketId;
        console.log('Selected market %s', marketId);
        cb(null, "OK");
    });
}

function getMarketPrices(cb) {
    console.log('Call getMarketPricesCompressed for marketId="%s"', marketId);
    var inv = session.getMarketPricesCompressed(marketId);
    inv.execute(function(err, res) {
        console.log('action:', res.action, 'error:', err, 'duration:',
                res.duration() / 1000);
        if (err) {
            cb("Error in getMarketPricesCompressed", null);
        }
        //console.log(util.inspect(res.result, false, 10));

        var market = res.result.marketPrices;
        console.log("marketId:", market.marketId);
        console.log("currency:", market.currency);
        console.log("marketStatus:", market.marketStatus);
        console.log("inPlayDelay:", market.inPlayDelay);
        // print players info
        for ( var playerIndex = 0; playerIndex < market.runners.length; ++playerIndex) {
            console.log("player %s", playerIndex);
            var runner = market.runners[playerIndex];
            console.log("\tselectionId:", runner.selectionId);
            console.log("\tlastPriceMatched:", runner.lastPriceMatched);
            console.log("\ttotalMatched:", runner.totalMatched);
            for ( var cnt = 0; cnt < runner.backPrices.length; ++cnt) {
                var item = runner.backPrices[cnt];
                console.log("\t back price:%s amount:%s", item.price, item.amount);
            }
            for ( var cnt = 0; cnt < runner.layPrices.length; ++cnt) {
                var item = runner.layPrices[cnt];
                console.log("\t lay price:%s amount:%s", item.price, item.amount);
            }
        }
        cb(null, "OK");
    });
}

function logout(callback) {
    console.log('logout');
    session.close(function(err, res) {
        if (err)
            console.log('logout failed, error is', err);
        else
            console.log('logout OK');
        callback(err, res);
    });
}

The result is

C:\Betfair>node getPrices.js
login to Betfair
login response: raw length=483, xml length=982, compression=51%
Session token is: [skipped]
login OK
Get available tennis matches
getAllMarkets response: raw length=7644, xml length=95548, compression=92%
action: getAllMarkets error: null duration: 0.157
Selected market 105672838
Call getMarketPricesCompressed for marketId="105672838"
getMarketPricesCompressed response: raw length=619, xml length=1242, compression=50%
action: getMarketPricesCompressed error: null duration: 0.109
marketId: 105672838
currency: USD
marketStatus: ACTIVE
inPlayDelay: 0
player 0
	selectionId: 2256470
	lastPriceMatched: 1.28
	totalMatched: 26.24
	 back price:1.28 amount:7.38
	 back price:1.14 amount:2346.53
	 back price:1.08 amount:15761.82
	 lay price:1.37 amount:150.0
	 lay price:1.87 amount:323.66
	 lay price: amount:undefined
player 1
	selectionId: 2519549
	lastPriceMatched: 
	totalMatched: 0.0
	 back price:3.75 amount:6.0
	 back price:1.06 amount:5.82
	 back price:1.05 amount:15765.68
	 lay price:4.7 amount:150.0
	 lay price: amount:undefined
	 lay price:undefined amount:undefined
logout
logout response: raw length=394, xml length=813, compression=52%
logout OK
# Step 8. Placing and Canceling Bets for the Market # Step 9. Updating Bets for the Market # Step 10. High-Performance Bot Programming

Conclusion. What is next?

I hope you got fun reading and possibly following this tutorial. JavaScript is a worth language to use for creating Betfair bots and you may want to learn it in systematic and thorough way. There are hundreds of shit books about JavaScript and I know only one that is good enough. It is the David Flanagan's "JavaScript, the Definitive Guide". It's worth read.

Any feedback is appreciated. Yours faithfully, AlgoTrader