diff --git a/README.md b/README.md index 552f361..b9c50dc 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This is the PHP wrapper for the Bitvavo API. This project can be used to build y * Price Ticker [REST](https://github.com/bitvavo/php-bitvavo-api#get-price-ticker) [Websocket](https://github.com/bitvavo/php-bitvavo-api#get-price-ticker-1) * Book Ticker [REST](https://github.com/bitvavo/php-bitvavo-api#get-book-ticker) [Websocket](https://github.com/bitvavo/php-bitvavo-api#get-book-ticker-1) * 24 Hour Ticker [REST](https://github.com/bitvavo/php-bitvavo-api#get-24-hour-ticker) [Websocket](https://github.com/bitvavo/php-bitvavo-api#get-24-hour-ticker-1) -* Private +* Private * Place Order [REST](https://github.com/bitvavo/php-bitvavo-api#place-order) [Websocket](https://github.com/bitvavo/php-bitvavo-api#place-order-1) * Update Order [REST](https://github.com/bitvavo/php-bitvavo-api#update-order) [Websocket](https://github.com/bitvavo/php-bitvavo-api#update-order-1) * Get Order [REST](https://github.com/bitvavo/php-bitvavo-api#get-order) [Websocket](https://github.com/bitvavo/php-bitvavo-api#get-order-1) @@ -66,7 +66,7 @@ The API key and secret are required for private calls and optional for public ca require_once('bitvavo.php'); $bitvavo = new Bitvavo([ - "APIKEY" => "", + "APIKEY" => "", "APISECRET" => "", "RESTURL" => "https://api.bitvavo.com/v2", "WSURL" => "wss://ws.bitvavo.com/v2/", @@ -84,6 +84,34 @@ $currentTime = $response["time"]; echo $currentTime ``` +### Rate limiting + +After every request [rate limits](https://docs.bitvavo.com/#section/Rate-limiting) are remembered, and can be subsequentially acquired by following method `$bitvavo->getRatelimit($key);`. Key can be one of `limit`, `remaining`, or `resetat`. Here is an example code that you can use to achieve high throughput without hitting a ban: + +```PHP +function handleRateimiting($bitvavo) { + + $remaining = $bitvavo->getRatelimit('remaining'); + + if (empty($remaining) || $remaining > 200) { + return; + } + + $resetat = $$bitvavo->getRatelimit('resetat'); + $resetat *= 1000; + $now = time() * 1000000; + $delay = $resetat - $now; + + // If the last request has been done a while ago. + if ($delay <= 0) { + return; + } + + // Have to use usleep() for sub-second resolution. + usleep($delay); +} +``` + ### General #### Get time @@ -1979,7 +2007,7 @@ Cancels all orders in a market. If no market is specified, all orders of an acco // options: market $websock->cancelOrders(["market" => "BTC-EUR"], function($response) { foreach ($response as $deletion) { - echo json_encode($deletion) . "\n"; + echo json_encode($deletion) . "\n"; } }); ``` @@ -2572,4 +2600,4 @@ $websock->subscriptionBook("BTC-EUR", function($response) { "nonce": 18632 } ``` - \ No newline at end of file + diff --git a/bitvavo.php b/bitvavo.php index f8ba8b6..41599d3 100644 --- a/bitvavo.php +++ b/bitvavo.php @@ -126,9 +126,55 @@ function createCurl($url, $method, $params) { curl_setopt($curl, CURLOPT_URL, $url); } curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_HEADER, true); + return $curl; } + function processRatelimit($header) { + + $this->ratelimit = []; + + $header = explode("\n", $header); + $header = array_map(function($line) {return trim($line);}, $header); + foreach ($header as $h) { + if (preg_match('/^bitvavo-ratelimit-(limit|remaining|resetat): +(\d+)$/', $h, $match)) { + list(, $key, $value) = $match; + $this->ratelimit[$key] = (int) $value; + } + } + } + + function getRatelimit($key) { + + if (!in_array($key, ['limit', 'remaining', 'resetat'])) { + errorToConsole("Invalid key ($key). Accepted valueas are: limit, remaining, resetat.\n"); + return; + } + + // No request has been performed. + if (empty($this->ratelimit)) { + return; + } + + // API has changed or is broken. + if (!array_key_exists($key, $this->ratelimit)) { + return; + } + + return $this->ratelimit[$key]; + } + + function execCurl($curl) { + $response = curl_exec($curl); + $header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE); + $header = substr($response, 0, $header_size); + $this->processRatelimit($header); + $body = substr($response, $header_size); + $json = json_decode($body, true); + return $json; + } + function sendPublic($url, $params, $method, $data) { $curl = $this->createCurl($url, $method, $params); $endpoint = str_replace(array($this->base), array(''), $url); @@ -150,9 +196,7 @@ function sendPublic($url, $params, $method, $data) { ); curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); } - $output = curl_exec($curl); - $json = json_decode($output, true); - return $json; + return $this->execCurl($curl); } function sendPrivate($endpoint, $params, $body, $method, $apiSecret, $base, $apiKey) { @@ -180,9 +224,7 @@ function sendPrivate($endpoint, $params, $body, $method, $apiSecret, $base, $api curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT"); curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($body)); } - $output = curl_exec($curl); - $json = json_decode($output, true); - return $json; + return $this->execCurl($curl); } public function time() { @@ -361,7 +403,7 @@ function sortAndInsert($update, $book, $sortFunc) { } class Websocket { - public function __construct($bitvavo = null, $reconnect = false, $publicCommandArray, $privateCommandArray, $oldSocket) { + public function __construct($bitvavo, $reconnect, $publicCommandArray, $privateCommandArray, $oldSocket) { $this->parent = $bitvavo; $this->wsurl = $bitvavo->wsurl; $this->apiKey = $bitvavo->apiKey; @@ -837,5 +879,3 @@ public function subscriptionBook($market, callable $callback) { $this->sendPublic(["action" => "getBook", "market" => $market]); } } - -?> \ No newline at end of file