├── .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 |
--------------------------------------------------------------------------------