├── .gitignore ├── BitMex.php ├── README.md ├── RSI-bot-example.php └── scheduler.bat /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /BitMex.php: -------------------------------------------------------------------------------- 1 | apiKey = $apiKey; 36 | $this->apiSecret = $apiSecret; 37 | 38 | $this->curlInit(); 39 | 40 | } 41 | 42 | /* 43 | * Public 44 | */ 45 | 46 | /* 47 | * Get Ticker 48 | * 49 | * @return ticker array 50 | */ 51 | 52 | public function getTicker() { 53 | 54 | $symbol = self::SYMBOL; 55 | $data['function'] = "instrument"; 56 | $data['params'] = array( 57 | "symbol" => $symbol 58 | ); 59 | 60 | $return = $this->publicQuery($data); 61 | 62 | if(!$return || count($return) != 1 || !isset($return[0]['symbol'])) return false; 63 | 64 | $return = array( 65 | "symbol" => $return[0]['symbol'], 66 | "last" => $return[0]['lastPrice'], 67 | "bid" => $return[0]['bidPrice'], 68 | "ask" => $return[0]['askPrice'], 69 | "high" => $return[0]['highPrice'], 70 | "low" => $return[0]['lowPrice'] 71 | ); 72 | 73 | return $return; 74 | 75 | } 76 | 77 | /* 78 | * Get Candles 79 | * 80 | * Get candles history 81 | * 82 | * @param $timeFrame can be 1m 5m 1h 83 | * @param $count candles count 84 | * @param $offset timestamp conversion offset in seconds 85 | * 86 | * @return candles array (from past to present) 87 | */ 88 | 89 | public function getCandles($timeFrame,$count,$offset = 0) { 90 | 91 | $symbol = self::SYMBOL; 92 | $data['function'] = "trade/bucketed"; 93 | $data['params'] = array( 94 | "symbol" => $symbol, 95 | "count" => $count, 96 | "binSize" => $timeFrame, 97 | "partial" => "false", 98 | "reverse" => "true" 99 | ); 100 | 101 | $return = $this->publicQuery($data); 102 | 103 | $candles = array(); 104 | $candleI = 0; 105 | // Converting 106 | foreach($return as $item) { 107 | 108 | $time = strtotime($item['timestamp']) + $offset; // Unix time stamp 109 | 110 | $candles[$candleI] = array( 111 | 'timestamp' => date('Y-m-d H:i:s',$time), // Local time human-readable time stamp 112 | 'time' => $time, 113 | 'open' => $item['open'], 114 | 'high' => $item['high'], 115 | 'close' => $item['close'], 116 | 'low' => $item['low'] 117 | ); 118 | $candleI++; 119 | } 120 | return $candles; 121 | 122 | } 123 | 124 | /* 125 | * Get Order 126 | * 127 | * Get order by order ID 128 | * 129 | * @return array or false 130 | */ 131 | 132 | public function getOrder($orderID,$count = 100) { 133 | 134 | $symbol = self::SYMBOL; 135 | $data['method'] = "GET"; 136 | $data['function'] = "order"; 137 | $data['params'] = array( 138 | "symbol" => $symbol, 139 | "count" => $count, 140 | "reverse" => "true" 141 | ); 142 | 143 | $orders = $this->authQuery($data); 144 | 145 | foreach($orders as $order) { 146 | if($order['orderID'] == $orderID) { 147 | return $order; 148 | } 149 | } 150 | 151 | return false; 152 | 153 | } 154 | 155 | /* 156 | * Get Orders 157 | * 158 | * Get last 100 orders 159 | * 160 | * @return orders array (from the past to the present) 161 | */ 162 | 163 | public function getOrders($count = 100) { 164 | 165 | $symbol = self::SYMBOL; 166 | $data['method'] = "GET"; 167 | $data['function'] = "order"; 168 | $data['params'] = array( 169 | "symbol" => $symbol, 170 | "count" => $count, 171 | "reverse" => "true" 172 | ); 173 | 174 | return array_reverse($this->authQuery($data)); 175 | } 176 | 177 | /* 178 | * Get Open Orders 179 | * 180 | * Get open orders from the last 100 orders 181 | * 182 | * @return open orders array 183 | */ 184 | 185 | public function getOpenOrders() { 186 | 187 | $symbol = self::SYMBOL; 188 | $data['method'] = "GET"; 189 | $data['function'] = "order"; 190 | $data['params'] = array( 191 | "symbol" => $symbol, 192 | "reverse" => "true" 193 | ); 194 | 195 | $orders = $this->authQuery($data); 196 | 197 | $openOrders = array(); 198 | foreach($orders as $order) { 199 | if($order['ordStatus'] == 'New' || $order['ordStatus'] == 'PartiallyFilled') $openOrders[] = $order; 200 | } 201 | 202 | return $openOrders; 203 | 204 | } 205 | 206 | /* 207 | * Get Open Positions 208 | * 209 | * Get all your open positions 210 | * 211 | * @return open positions array 212 | */ 213 | 214 | public function getOpenPositions() { 215 | 216 | $symbol = self::SYMBOL; 217 | $data['method'] = "GET"; 218 | $data['function'] = "position"; 219 | $data['params'] = array( 220 | "symbol" => $symbol 221 | ); 222 | 223 | $positions = $this->authQuery($data); 224 | 225 | $openPositions = array(); 226 | foreach($positions as $position) { 227 | if(isset($position['isOpen']) && $position['isOpen'] == true) { 228 | $openPositions[] = $position; 229 | } 230 | } 231 | 232 | return $openPositions; 233 | } 234 | 235 | /* 236 | * Close Position 237 | * 238 | * Close open position 239 | * 240 | * @return array 241 | */ 242 | 243 | public function closePosition($price) { 244 | 245 | $symbol = self::SYMBOL; 246 | $data['method'] = "POST"; 247 | $data['function'] = "order/closePosition"; 248 | $data['params'] = array( 249 | "symbol" => $symbol, 250 | "price" => $price 251 | ); 252 | 253 | return $this->authQuery($data); 254 | } 255 | 256 | /* 257 | * Edit Order Price 258 | * 259 | * Edit you open order price 260 | * 261 | * @param $orderID Order ID 262 | * @param $price new price 263 | * 264 | * @return new order array 265 | */ 266 | 267 | public function editOrderPrice($orderID,$price) { 268 | 269 | $data['method'] = "PUT"; 270 | $data['function'] = "order"; 271 | $data['params'] = array( 272 | "orderID" => $orderID, 273 | "price" => $price 274 | ); 275 | 276 | return $this->authQuery($data); 277 | } 278 | 279 | /* 280 | * Create Order 281 | * 282 | * Create new market order 283 | * 284 | * @param $type can be "Limit" 285 | * @param $side can be "Buy" or "Sell" 286 | * @param $price BTC price in USD 287 | * @param $quantity should be in USD (number of contracts) 288 | * @param $maker forces platform to complete your order as a 'maker' only 289 | * 290 | * @return new order array 291 | */ 292 | 293 | public function createOrder($type,$side,$price,$quantity,$maker = false) { 294 | 295 | $symbol = self::SYMBOL; 296 | $data['method'] = "POST"; 297 | $data['function'] = "order"; 298 | $data['params'] = array( 299 | "symbol" => $symbol, 300 | "side" => $side, 301 | "price" => $price, 302 | "orderQty" => $quantity, 303 | "ordType" => $type 304 | ); 305 | 306 | if($maker) { 307 | $data['params']['execInst'] = "ParticipateDoNotInitiate"; 308 | } 309 | 310 | return $this->authQuery($data); 311 | } 312 | 313 | /* 314 | * Cancel All Open Orders 315 | * 316 | * Cancels all of your open orders 317 | * 318 | * @param $text is a note to all closed orders 319 | * 320 | * @return all closed orders arrays 321 | */ 322 | 323 | public function cancelAllOpenOrders($text = "") { 324 | 325 | $symbol = self::SYMBOL; 326 | $data['method'] = "DELETE"; 327 | $data['function'] = "order/all"; 328 | $data['params'] = array( 329 | "symbol" => $symbol, 330 | "text" => $text 331 | ); 332 | 333 | return $this->authQuery($data); 334 | } 335 | 336 | /* 337 | * Get Wallet 338 | * 339 | * Get your account wallet 340 | * 341 | * @return array 342 | */ 343 | 344 | public function getWallet() { 345 | 346 | $data['method'] = "GET"; 347 | $data['function'] = "user/wallet"; 348 | $data['params'] = array( 349 | "currency" => "XBt" 350 | ); 351 | 352 | return $this->authQuery($data); 353 | } 354 | 355 | /* 356 | * Get Margin 357 | * 358 | * Get your account margin 359 | * 360 | * @return array 361 | */ 362 | 363 | public function getMargin() { 364 | 365 | $data['method'] = "GET"; 366 | $data['function'] = "user/margin"; 367 | $data['params'] = array( 368 | "currency" => "XBt" 369 | ); 370 | 371 | return $this->authQuery($data); 372 | } 373 | 374 | /* 375 | * Get Order Book 376 | * 377 | * Get L2 Order Book 378 | * 379 | * @return array 380 | */ 381 | 382 | public function getOrderBook($depth = 25) { 383 | 384 | $symbol = self::SYMBOL; 385 | $data['method'] = "GET"; 386 | $data['function'] = "orderBook/L2"; 387 | $data['params'] = array( 388 | "symbol" => $symbol, 389 | "depth" => $depth 390 | ); 391 | 392 | return $this->authQuery($data); 393 | } 394 | 395 | /* 396 | * Set Leverage 397 | * 398 | * Set position leverage 399 | * $leverage = 0 for cross margin 400 | * 401 | * @return array 402 | */ 403 | 404 | public function setLeverage($leverage) { 405 | 406 | $symbol = self::SYMBOL; 407 | $data['method'] = "POST"; 408 | $data['function'] = "position/leverage"; 409 | $data['params'] = array( 410 | "symbol" => $symbol, 411 | "leverage" => $leverage 412 | ); 413 | 414 | return $this->authQuery($data); 415 | } 416 | 417 | /* 418 | * Private 419 | * 420 | */ 421 | 422 | /* 423 | * Auth Query 424 | * 425 | * Query for authenticated queries only 426 | * 427 | * @param $data consists method (GET,POST,DELETE,PUT),function,params 428 | * 429 | * @return return array 430 | */ 431 | 432 | private function authQuery($data) { 433 | 434 | $method = $data['method']; 435 | $function = $data['function']; 436 | if($method == "GET" || $method == "POST" || $method == "PUT") { 437 | $params = http_build_query($data['params']); 438 | } 439 | elseif($method == "DELETE") { 440 | $params = json_encode($data['params']); 441 | } 442 | $path = self::API_PATH . $function; 443 | $url = self::API_URL . self::API_PATH . $function; 444 | if($method == "GET" && count($data['params']) >= 1) { 445 | $url .= "?" . $params; 446 | $path .= "?" . $params; 447 | } 448 | $nonce = $this->generateNonce(); 449 | if($method == "GET") { 450 | $post = ""; 451 | } 452 | else { 453 | $post = $params; 454 | } 455 | 456 | $sign = hash_hmac('sha256', $method.$path.$nonce.$post, $this->apiSecret); 457 | 458 | $headers = array(); 459 | 460 | $headers[] = "api-signature: $sign"; 461 | $headers[] = "api-key: {$this->apiKey}"; 462 | $headers[] = "api-nonce: $nonce"; 463 | 464 | $headers[] = 'Connection: Keep-Alive'; 465 | $headers[] = 'Keep-Alive: 90'; 466 | 467 | curl_reset($this->ch); 468 | curl_setopt($this->ch, CURLOPT_URL, $url); 469 | if($data['method'] == "POST") { 470 | curl_setopt($this->ch, CURLOPT_POST, true); 471 | curl_setopt($this->ch, CURLOPT_POSTFIELDS, $post); 472 | } 473 | if($data['method'] == "DELETE") { 474 | curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, "DELETE"); 475 | curl_setopt($this->ch, CURLOPT_POSTFIELDS, $post); 476 | $headers[] = 'X-HTTP-Method-Override: DELETE'; 477 | } 478 | if($data['method'] == "PUT") { 479 | curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, "PUT"); 480 | //curl_setopt($this->ch, CURLOPT_PUT, true); 481 | curl_setopt($this->ch, CURLOPT_POSTFIELDS, $post); 482 | $headers[] = 'X-HTTP-Method-Override: PUT'; 483 | } 484 | curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers); 485 | curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER , false); 486 | curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); 487 | $return = curl_exec($this->ch); 488 | 489 | if(!$return) { 490 | $this->curlError(); 491 | $this->error = true; 492 | return false; 493 | } 494 | 495 | $return = json_decode($return,true); 496 | 497 | if(isset($return['error'])) { 498 | $this->platformError($return); 499 | $this->error = true; 500 | return false; 501 | } 502 | 503 | $this->error = false; 504 | $this->errorCode = false; 505 | $this->errorMessage = false; 506 | 507 | return $return; 508 | 509 | } 510 | 511 | /* 512 | * Public Query 513 | * 514 | * Query for public queries only 515 | * 516 | * @param $data consists function,params 517 | * 518 | * @return return array 519 | */ 520 | 521 | private function publicQuery($data) { 522 | 523 | $function = $data['function']; 524 | $params = http_build_query($data['params']); 525 | $url = self::API_URL . self::API_PATH . $function . "?" . $params;; 526 | 527 | $headers = array(); 528 | 529 | $headers[] = 'Connection: Keep-Alive'; 530 | $headers[] = 'Keep-Alive: 90'; 531 | 532 | curl_reset($this->ch); 533 | curl_setopt($this->ch, CURLOPT_URL, $url); 534 | curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER , false); 535 | curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); 536 | curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers); 537 | $return = curl_exec($this->ch); 538 | 539 | if(!$return) { 540 | $this->curlError(); 541 | $this->error = true; 542 | return false; 543 | } 544 | 545 | $return = json_decode($return,true); 546 | 547 | if(isset($return['error'])) { 548 | $this->platformError($return); 549 | $this->error = true; 550 | return false; 551 | } 552 | 553 | $this->error = false; 554 | $this->errorCode = false; 555 | $this->errorMessage = false; 556 | 557 | return $return; 558 | 559 | } 560 | 561 | /* 562 | * Generate Nonce 563 | * 564 | * @return string 565 | */ 566 | 567 | private function generateNonce() { 568 | 569 | $nonce = (string) number_format(round(microtime(true) * 100000), 0, '.', ''); 570 | 571 | return $nonce; 572 | 573 | } 574 | 575 | /* 576 | * Curl Init 577 | * 578 | * Init curl header to support keep-alive connection 579 | */ 580 | 581 | private function curlInit() { 582 | 583 | $this->ch = curl_init(); 584 | 585 | } 586 | 587 | /* 588 | * Curl Error 589 | * 590 | * @return false 591 | */ 592 | 593 | private function curlError() { 594 | 595 | if ($errno = curl_errno($this->ch)) { 596 | $this->errorCode = $errno; 597 | $errorMessage = curl_strerror($errno); 598 | $this->errorMessage = $errorMessage; 599 | if($this->printErrors) echo "cURL error ({$errno}) : {$errorMessage}\n"; 600 | return true; 601 | } 602 | 603 | return false; 604 | } 605 | 606 | /* 607 | * Platform Error 608 | * 609 | * @return false 610 | */ 611 | 612 | private function platformError($return) { 613 | 614 | $this->errorCode = $return['error']['name']; 615 | $this->errorMessage = $return['error']['message']; 616 | if($this->printErrors) echo "BitMex error ({$return['error']['name']}) : {$return['error']['message']}\n"; 617 | 618 | return true; 619 | } 620 | 621 | } 622 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitmex-api-php 2 | BitMex PHP REST API with HTTP Keep-Alive support 3 | 4 | Get API keys from https://www.bitmex.com/app/apiKeys 5 | 6 | HTTP Keep-Alive: BitMex says that "When using HTTP Keep-Alive, request/response round-trip time will be identical to Websocket" 7 | 8 | ## Usage Example 9 | createOrder("Limit","Sell",50000,1000)); 18 | ?> 19 | 20 | ## Donations 21 | Your BitCoin donations are highly appreciated at [1N36HHos4qQ76PX1BrmeaJCzWDmggreuNU](https://blockchain.info/address/1N36HHos4qQ76PX1BrmeaJCzWDmggreuNU) 22 | -------------------------------------------------------------------------------- /RSI-bot-example.php: -------------------------------------------------------------------------------- 1 | getCandles($timeFrame,$count); 12 | var_dump($hist); 13 | echo "

"; 14 | //calc RSI 15 | $totalGain = 0; 16 | $totalLoss = 0; 17 | 18 | for($i=0;$i<21;$i++){ 19 | $compareGreater = $hist[$i]["close"] > $hist[$i+1]["close"]; 20 | $compareLess = $hist[$i]["close"] < $hist[$i+1]["close"]; 21 | 22 | $gainCalc = $hist[$i]["close"] - $hist[$i+1]["close"]; 23 | $lossCalc = $hist[$i+1]["close"] - $hist[$i]["close"]; 24 | 25 | if($compareGreater){ 26 | $totalGain += $gainCalc; 27 | echo "Gain ".$gainCalc."
"; 28 | } 29 | 30 | if($compareLess){ 31 | $totalLoss += $lossCalc; 32 | echo "Loss ".$lossCalc."
"; 33 | } 34 | 35 | } 36 | 37 | $averageGain = $totalGain / 21; 38 | echo "Avg Gain ".$averageGain."
"; 39 | $averageLoss = $totalLoss / 21; 40 | echo "Avg Loss ".$averageLoss."
"; 41 | 42 | if($averageLoss == 0){ 43 | $rsi = 100; 44 | } else { 45 | //calc and normalize 46 | $rs = $averageGain / $averageLoss; 47 | $rsi = 100 - (100 / ( 1 + $rs)); 48 | } 49 | 50 | echo "RSI: ".$rsi; 51 | 52 | //output to log file 53 | $logTime = time().".txt"; 54 | $openLog = fopen($logTime, "w"); 55 | $inputData = $rsi; 56 | fwrite($openLog, $inputData); 57 | fclose($openLog); 58 | 59 | 60 | //execute a trade 61 | if($rsi < 13){ 62 | //close any open position 63 | $bitmex->closePosition(null); 64 | //null is used to specify close the current position at market 65 | //alternatively you can specify a limit price to close at 66 | 67 | //buy 68 | $buy = $Bitmex->createOrder("Market", "Buy", null, 10000); 69 | //null is used in place of a limit price, if you wanted to place a limit order change "Market" to "Limit" and specify the limit price in the 3rd position. 70 | } 71 | 72 | 73 | //close position if ROE is over N% 74 | 75 | //get current position details 76 | $positions = $bitmex->getOpenPositions(); 77 | $pnl = $positions[0]["unrealisedRoePcnt"]; 78 | 79 | //if ROE is 12% or higher close position 80 | if($pnl > 0.12){ 81 | $bitmex->closePosition(null); 82 | } 83 | 84 | ?> 85 | -------------------------------------------------------------------------------- /scheduler.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | C:\xampp7\php\php.exe -f "C:\xampp7\htdocs\bitmex\index.php" 3 | --------------------------------------------------------------------------------