BTC-e Trading Bot Written in PHP

Nowadays, the popularity of bitcoin (BTC) is increasing tremendously, there are quite a few BTC excahge sites on the web. After experiencing certain difficulties with MtGox, people started looking for alternatives, and one of them was a Russian exchange https://btc-e.com

These days when bitcoin increased like 10 times in two weeks, people started actively looking for automated trading solutions. I’ve been trying to find a BTC trading bot that I can modify and couldn’t find a decent one, so I decided to write my own as BTC provides an API

I’ll give you my code and comments below, but before that I’d like to note that I am NOT responsible for any financial losses, direct or indirect, caused by this software, you use it at your own risk!

Also, it’s worth noticing that it doesn’t make sense to launch it when we see a huge exchange rate increase like it was in the end of November 2013 – beginning of Dec 2013. If you’re sure or just know 🙂 that the rate will be 10x more than today, of course, you’d rather save your BTC’s and trade them when it’s at its peak.

First, we’d need a database to save current market depth and log our transactions. Here is the dump of these two tables:

DROP TABLE IF EXISTS `depth_log`;

CREATE TABLE `depth_log` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `total_usd` FLOAT(13,5) NOT NULL,
  `avg_bid` FLOAT(10,5) NOT NULL,
  `total_btc` FLOAT(12,5) NOT NULL,
  `avg_btc` FLOAT(10,5) NOT NULL,
  `abs` FLOAT(10,5) NOT NULL,
  `current_rate_bid` FLOAT(10,5) NOT NULL,
  `current_rate_ask` FLOAT(10,5) NOT NULL,
  `flag_buy` TINYINT(1) DEFAULT NULL,
  `flag_sell` TINYINT(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MYISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `transactions`;
CREATE TABLE `transactions` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `type` ENUM('buy','sell') NOT NULL,
  `amount_btc` FLOAT(10,5) NOT NULL,
  `amount_usd` FLOAT(10,5) NOT NULL,
  `rate` FLOAT(10,5) NOT NULL,
  `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MYISAM DEFAULT CHARSET=utf8;

In order to use trade API calls at BTC-e, you must be registered and obtain your keys under your profile section. Please do so and below you can find the beginning of our bot file:

error_reporting(E_ALL ^ E_DEPRECATED);
ini_set('display_errors',1);
 
//mysql connection to the DB we created above. Note that mysql_* extension is deprecated as of today and will soon be removed
mysql_connect('localhost','user','password');
mysql_select_db('dbname');
 
// we will loop through market depth starting from the cheapest, until we reach the amount we can buy 5 BTC's for
define('BTC_AMOUNT_TO_REFLECT_PRICE',5);
// we will loop through market depth starting from the cheapest, until we reach the amount we can buy 1000 USD's for
define('USD_AMOUNT_TO_REFLECT_PRICE',1000);
 
//when our indicator (see below the code) increases more than 500, we should sell
//when our indicator (see below the code) goes below  -500, we should buy
define('SELL_THRESHOLD',500);
define('BUY_THRESHOLD',-500);
 
date_default_timezone_set('Europe/Berlin');
 
//our API keys
define('API_KEY','XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXXX');
define('API_SECRET','XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');

We can analyze market depth at BTC-e using this URL:

$depth = json_decode(file_get_contents('https://btc-e.com/api/2/btc_usd/depth'));

Also, here is the function performing API calls:

function btce_query($method, array $req = array()) {
    try {
        // API settings
        $key = API_KEY; // your API-key
        $secret = API_SECRET; // your Secret-key
 
        $req['method'] = $method;
        $mt = explode(' ', microtime());
        $req['nonce'] = $mt[1];
 
        // generate the POST data string
        $post_data = http_build_query($req, '', '&');
 
        $sign = hash_hmac('sha512', $post_data, $secret);
 
        // generate the extra headers
        $headers = array(
                        'Sign: '.$sign,
                        'Key: '.$key,
        );
 
        // our curl handle (initialize if required)
        static $ch = null;
        if (is_null($ch)) {
                $ch = curl_init();
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; BTCE PHP client; '.php_uname('s').'; PHP/'.phpversion().')');
        }
        curl_setopt($ch, CURLOPT_URL, 'https://btc-e.com/tapi/');
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
 
        // run the query
        $res = curl_exec($ch);
        if ($res === false) throw new Exception('Could not get reply: '.curl_error($ch));
        $dec = json_decode($res, true);
        if (!$dec) throw new Exception('Invalid data received, please make sure connection is working and requested API exists');
        return $dec;
    } catch(Exception $e) {echo 'got Exception: ' . $e->getMessage() . PHP_EOL;}
}

and since it’s a bot, we should put all the actions in an infinite loop:

while(1) {
    //get current market depth
    $depth = json_decode(file_get_contents('https://btc-e.com/api/2/btc_usd/depth'));
 
    $sum_usd = $sum_btc = $usd = $btc = $current_rate_btc = $current_rate_usd = 0;
    // loop through all bids (sorted ascending) until we reach BTC_AMOUNT_TO_REFLECT_PRICE  constant
    if(is_array($depth->bids) && is_array($depth->asks)) {
        foreach($depth->bids as $bid) {
            $btc += $bid[1];
            if($btc >= BTC_AMOUNT_TO_REFLECT_PRICE && !$current_rate_btc) {
                $current_rate_btc = $bid[0];
            }
            $sum_usd += $bid[0]  * $bid[1];
        }
 
        $bids_avg = $sum_usd / count($depth->bids);
    // loop through all asks (sorted ascending) until we reach USD_AMOUNT_TO_REFLECT_PRICE constant
        foreach($depth->asks as $ask) {
            $usd += $ask[0];
            if($usd >= USD_AMOUNT_TO_REFLECT_PRICE && !$current_rate_usd) {
                $current_rate_usd = $ask[0];
            }
            $sum_btc += $ask[0]  * $ask[1];
        }
 
        $asks_avg = $sum_btc / count($depth->asks);
 
        ////////////////////////////////////////////
        // get last 3 previous records (last 15 seconds) from depth_log table to get our indicator - whether to buy or sell
        $res = mysql_query('SELECT * FROM `depth_log` ORDER BY `id` desc limit 3');
 
        $flag_buy = $flag_sell = true;
        while(($row = mysql_fetch_assoc($res)) !== false) {
            if($row['abs'] > BUY_THRESHOLD) {
                $flag_buy = false;
            }
            if($row['abs'] < SELL_THRESHOLD) {
                $flag_sell = false;
            }
        }
        // debugging output
        echo 'Date: ' . date('Y-m-d H:i:s') . '; flag_buy: ' . (int)$flag_buy . '; flag_sell: ' . (int)$flag_sell . PHP_EOL;
        ////////////////////////////////////////////
 
        // save current market depth with our indicator ($asks_avg - $bids_avg)
        mysql_query(sprintf('INSERT INTO `depth_log`(`total_usd`,`avg_bid`,`total_btc`,`avg_btc`,`abs`,`current_rate_bid`,`current_rate_ask`,`flag_buy`,`flag_sell`) VALUES(%.5f,%.5f,%.5f,%.5f,%.5f,%.5f,%.5f,%d,%d)',
                    mysql_real_escape_string($sum_usd),
                    mysql_real_escape_string($bids_avg),
                    mysql_real_escape_string($sum_btc),
                    mysql_real_escape_string($asks_avg),
                    mysql_real_escape_string($asks_avg - $bids_avg),
                    mysql_real_escape_string($current_rate_btc),
                    mysql_real_escape_string($current_rate_usd),
                    mysql_real_escape_string($flag_buy),
                    mysql_real_escape_string($flag_sell)));
        // get our balances
        $info = btce_query('getInfo');
 
        if(is_array($info) && $info['success']) {
            $usd_balance = floatval($info['return']['funds']['usd']);
            $btc_balance = $info['return']['funds']['btc'];
 
            // if flag_buy AND we have USD's, then buy
            if($flag_buy && $usd_balance > 5) {
                $amount_btc = (0.9975 * $usd_balance ) / $current_rate_usd;
                $result = btce_query('Trade',array('pair' => 'btc_usd', 'type' => 'buy', 'rate' => round($current_rate_usd,2), 'amount' => round($amount_btc,6)));
 
                mysql_query(sprintf('INSERT INTO `transactions`(`type`,`amount_btc`,`amount_usd`,`rate`) VALUES("buy",%.5f,0,%.5f)',mysql_real_escape_string($amount_btc),$current_rate_usd));
            }
 
            // if flag_sell AND we have BTC's, then sell
            if($flag_sell && $btc_balance > 0.02) {
                $amount_btc = (0.9975 * $btc_balance);
                $result = btce_query('Trade',array('pair' => 'btc_usd', 'type' => 'sell', 'rate' => round($current_rate_btc,2), 'amount' => round($amount_btc,6)));
 
                mysql_query(sprintf('INSERT INTO `transactions`(`type`,`amount_btc`,`amount_usd`,`rate`) VALUES("sell",%.5f,0,%.5f)',mysql_real_escape_string($amount_btc),$current_rate_btc));
            }
        }
    }
    // sleep for 5 seconds
    sleep(5);
}

So te indicator is $asks_avg – $bids_avg which shows the current market trend (wither more BTC’s to sell or USD’s).

As a proof that it works for me, I have some historical logs from `depth_log`, let’s have a look:

id	timestamp	total_usd	avg_bid	total_btc	avg_btc	abs	current_rate_bid	current_rate_ask	flag_buy	flag_sell
3821	2013-10-26 22:56:29	168489.29688	1123.26196	60573.40625	403.82269	-719.43921	168.00000	171.62199	0	0
3822	2013-10-26 22:57:00	167299.65625	1115.33105	60700.45703	404.66971	-710.66132	168.00200	171.48900	0	0
3823	2013-10-26 22:57:30	163629.75000	1090.86499	59875.71484	399.17142	-691.69360	168.00200	171.49400	0	0
3824	2013-10-26 22:58:00	174217.96875	1161.45312	60469.49609	403.12997	-758.32312	168.10001	171.49200	1	0
3825	2013-10-26 22:58:30	175912.10938	1172.74744	61148.60547	407.65738	-765.09003	168.11400	171.40601	1	0
3826	2013-10-26 22:59:01	165750.96875	1105.00647	60603.46094	404.02307	-700.98334	169.72800	171.40700	1	0
3827	2013-10-26 22:59:31	167904.75000	1119.36499	61039.70703	406.93137	-712.43359	170.00200	171.40500	1	0
3828	2013-10-26 23:00:01	167775.29688	1118.50195	61842.56250	412.28375	-706.21826	170.00200	171.40199	1	0

So assume we buy at $171.407 on 2013-10-26 22:59:01, and then:

id	timestamp	total_usd	avg_bid	total_btc	avg_btc	abs	current_rate_bid	current_rate_ask	flag_buy	flag_sell
6243	2013-10-27 18:18:49	25856.59961	172.37733	125895.38281	839.30255	666.92517	177.50600	179.13000	0	0
6244	2013-10-27 18:19:23	25841.80664	172.27872	123188.04688	821.25366	648.97491	177.50600	179.07401	0	0
6245	2013-10-27 18:19:53	29259.03516	195.06024	157349.15625	1048.99438	853.93414	177.50600	178.99800	0	0
6246	2013-10-27 18:20:23	29156.33398	194.37556	156593.57812	1043.95728	849.58167	177.50600	179.00000	0	1
6247	2013-10-27 18:20:53	26559.46094	177.06306	157610.73438	1050.73816	873.67511	177.50999	178.99500	0	1
6248	2013-10-27 18:21:23	30973.12305	206.48749	156826.71875	1045.51147	839.02393	177.14200	178.59801	0	1

Sell it on 2013-10-27 18:21:23 for $177.14200. So in 18 hours the profit was ~5.7%, we should subtract also 0.4% as commission at BTC-e is 0.2% for every transaction, so roughly 5%.

For those who are interested, here is some data from my depth_log. The whole bot source can be found here

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.