Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?php
- class KrakenTraderException extends Exception{}
- class KrakenTrader{
- protected $kraken;
- protected $set;
- protected $currentRate;
- protected $lastPrivateApiCallMicrotime;
- protected $totalMargin;
- protected $totalProfit;
- protected $totalFees;
- const BUY = 'buy';
- const SELL = 'sell';
- public function __construct($key, $secret){
- $url = 'https://api.kraken.com';
- $sslverify = true;
- $version = 0;
- $this->kraken = new KrakenAPI($key, $secret, $url, $version, $sslverify);
- $this->set = array();
- $this->lastPrivateApiCallMicrotime = 0;
- $this->totalMargin = 0;
- $this->totalProfit = 0;
- $this->totalFees = 0;
- }
- protected function apiLog($res){
- $this->log($res, '-api');
- }
- protected function objectLog($res){
- $this->log($res, '-object');
- }
- protected function log($message, $suffix = ''){
- $filename = 'kraken-trade' . $suffix . '.log';
- if (!$handle = fopen(dirname(__FILE__) . DIRECTORY_SEPARATOR . $filename, 'a+')){
- throw new KrakenTraderException('can\'t open file ' . dirname(__FILE__) . DIRECTORY_SEPARATOR . $filename . '. Make sure it\'s writable for PHP.');
- }
- if (fwrite($handle, json_encode(array('date' => date('Y-m-d H:i:s'), 'msg' => $message)) . PHP_EOL) === FALSE) {
- throw new KrakenTraderException('can\'t write to file ' . dirname(__FILE__) . DIRECTORY_SEPARATOR . $filename . '. Make sure it\'s writable for PHP.');
- }
- fclose($handle);
- }
- /**
- * Get the fee charged by Kraken
- * @todo get current fee from API
- *
- * @param float $price
- */
- public static function getFee($price){
- return $price / 100 * 0.11;
- }
- /**
- * Add a buy/sell set
- *
- * @param float $buyRate in Euros
- * @param float $margin percentage (1 = 1%, 10 = 10%, 0.5 = 0.5%)
- * @param float $bitcoins in BTC (0.01 minimum)
- * @param string $buySell buy|sell constant
- * @param bool $placeOrderAtAnyRate
- */
- public function addSet($buyRate, $margin, $bitcoins, $buySell = self::BUY, $placeOrderAtAnyRate = false){
- $trade = new KrakenTradeSet($buyRate, $margin, $bitcoins, $buySell, count($this->set)+1, $placeOrderAtAnyRate);
- $this->set[] = $trade;
- $this->log(array(
- 'index' => $trade->getIndex(),
- 'startWith' => $trade->getBuySell(),
- 'margin' => $trade->getMargin(),
- 'bitcoins' => $trade->getBitcoins(),
- 'buyRate' => $trade->getBuyRate(),
- 'sellAt' => round($trade->getBuyRate() + ($trade->getBuyRate() / 100 * $trade->getMargin()), 5),
- 'atAnyRate' => $trade->getPlaceOrderAtAnyRate()
- ));
- }
- /**
- * Get the the price at which last trade was made
- * And log changes in the current rate
- *
- */
- public function getCurrentRate(){
- if (!$res = KrakenApiCache::get('Ticker')){
- // cache for 1 second
- $res = $this->QueryPublic('Ticker', array('pair' => 'XXBTZEUR'));
- $this->apiLog($res);
- KrakenApiCache::set('Ticker', $res, 1);
- }
- if (!isset($res['result']['XXBTZEUR']['c'][0])){
- throw new KrakenTraderException('Not a valid API response for current rate');
- }
- $rate = $res['result']['XXBTZEUR']['c'][0];
- if ($rate !== $this->currentRate){
- // only log the rate at a change
- $this->log('current rate: ' . $rate);
- }
- $this->currentRate = $rate;
- return $rate;
- }
- /**
- * return trade data since given id (exclusive)
- *
- * @param string $sinceId
- */
- public function getRecentTrades($sinceId = null){
- if (!$res = KrakenApiCache::get('Trades')){
- // cache for 1 second
- $options = array('pair' => 'XXBTZEUR');
- if (!is_null($sinceId)){
- $options['since'] = $sinceId;
- }
- $res = $this->QueryPublic('Trades', $options);
- $this->apiLog($res);
- KrakenApiCache::set('Trades', $res, 1);
- }
- if (!isset($res['result']['XXBTZEUR'])){
- throw new KrakenTraderException('Not a valid API response for recent trades');
- }
- $trades = array();
- foreach($res['result']['XXBTZEUR'] as $trade){
- $trades[] = array(
- 'price' => $trade[0],
- 'volume' => $trade[1],
- 'time' => date('Y-m-d H:i:s', $trade[2]),
- 'buySell' => $trade[3],
- 'marketLimit' => $trade[4],
- );
- }
- return $trades;
- }
- /**
- * interface API with 5 tries before exit
- *
- * @param string $method
- * @param array $request
- * @return array
- */
- private function queryPublic($method, array $request = array()){
- static $apiErrors = 0;
- try {
- $res = $this->kraken->QueryPublic($method, $request);
- } catch(Exception $e){
- $apiErrors++;
- $this->log('Kraken queryPublic Error #' . $apiErrors . ' on ' . $method . ': ' . print_r($e->getMessage(), true));
- if($apiErrors >= 5){
- throw new KrakenTraderException('>=5 API errors queryPublic on ' . $method . ' ' . $e->getMessage());
- }
- sleep(1);
- // try again
- return $this->queryPublic($method, $request);
- }
- $apiErrors = 0;
- return $res;
- }
- /**
- * interface API with 3 tries before exit
- *
- * @param string $method
- * @param array $request
- * @return array
- */
- private function queryPrivate($method, array $request = array()){
- static $apiErrors = 0;
- try {
- $res = $this->kraken->queryPrivate($method, $request);
- } catch(Exception $e){
- $apiErrors++;
- $this->log('Kraken queryPrivate Error #' . $apiErrors . ' on ' . $method . ': ' . print_r($e->getMessage(), true));
- if($apiErrors >= 3){
- throw new KrakenTraderException('>=3 API errors queryPrivate on ' . $method . ' ' . $e->getMessage());
- }
- sleep(5);
- // try again
- return $this->queryPrivate($method, $request);
- }
- $apiErrors = 0;
- return $res;
- }
- public function analyseTrades($margin = 0.52, $step = 0.05){
- $trades = $this->getRecentTrades();
- $lowest = $trades[0]['price'];
- $highest = $trades[0]['price'];
- foreach($trades as $trade){
- if ($trade['price'] < $lowest){
- $lowest = $trade['price'];
- }
- if ($trade['price'] > $highest){
- $highest = $trade['price'];
- }
- }
- echo date('Y-m-d H:i:s') . PHP_EOL;
- echo 'lowest :' . $lowest . PHP_EOL;
- echo 'highest :' . $highest . PHP_EOL;
- flush();
- ob_flush();
- $result = array();
- for ($current = $lowest; $current < $highest - ($highest / 100 * $margin); $current += $step){
- $buyAt = round($current, 3);
- $sellAt = round($current + ($current / 100 * $margin), 3);
- //echo $buyAt . ' ' . $sellAt . PHP_EOL;
- flush();
- ob_flush();
- $buyCounter = 0;
- $sellCounter = 0;
- $volumeBelowBuy = 0;
- $volumeAboveSell = 0;
- foreach($trades as $trade){
- if ($trade['price'] <= $buyAt){
- $volumeBelowBuy += $trade['volume'];
- $buyCounter++;
- }
- if ($trade['price'] >= $sellAt){
- $volumeAboveSell += $trade['volume'];
- $sellCounter++;
- }
- }
- $result[] = array(
- 'buyAt' => $buyAt,
- 'sellAt' => $sellAt,
- 'sellCounter' => $sellCounter,
- 'buyCounter' => $buyCounter,
- 'volumeBelowBuy' => $volumeBelowBuy,
- 'volumeAboveSell' => $volumeAboveSell
- );
- }
- usort($result, array('self', 'sortTradeResult'));
- echo '<pre>';
- print_r($result);
- exit;
- return $result;
- }
- /* This is the static comparing function: */
- protected static function sortTradeResult($a, $b){
- $aMax = max(array($a['sellCounter'], $a['buyCounter']));
- $aMin = min(array($a['sellCounter'], $a['buyCounter']));
- $result = $aMax - $aMin;
- if ($result == 0){
- $result = 0.1;
- }
- $a = $aMax / $result;
- $bMax = max(array($b['sellCounter'], $b['buyCounter']));
- $bMin = min(array($b['sellCounter'], $b['buyCounter']));
- $result = $bMax - $bMin;
- if ($result == 0){
- $result = 0.1;
- }
- $b = $bMax / $result;
- return ($a < $b) ? 1 : -1;
- }
- /**
- * Check if order is in open Orders list
- * Log partial executed orders (still in open orders)
- *
- * @param KrakenTradeSet $trade
- * @param string $txid
- */
- protected function inOpenOrders(KrakenTradeSet &$trade, $txid){
- if (!$res = KrakenApiCache::get('OpenOrders')){
- if (microtime(true) - $this->lastPrivateApiCallMicrotime <= 5){
- // sleep for 5 seconds
- // kraken does not allow fast orders :(
- usleep(5 * 1000000);
- }
- $this->lastPrivateApiCallMicrotime = microtime(true);
- // cache the output of the Kraken API for 5 seconds to prevent rate limit errors
- $res = $this->QueryPrivate('OpenOrders', array('trades' => true));
- $this->apiLog($res);
- KrakenApiCache::set('OpenOrders', $res, 5);
- }
- if (!isset($res['result']['open'])){
- throw new KrakenTraderException('Not a valid API result on OpenOrders');
- }
- if (isset($res['result']['open'][$txid])){
- $order = $res['result']['open'][$txid];
- if ($trade->getVolumeExecuted() != $order['vol_exec'] && $order['vol_exec'] > 0 && $order['vol_exec'] < $trade->getBitcoins()){
- // log partial executed orders. Only log a change in execution
- $trade->setVolumeExecuted($order['vol_exec']);
- $this->log('#' . $trade->getIndex() . ' partial order executed ' . $order['vol_exec'] . ' of ' . $trade->getBitcoins() . ' XBTEUR @ limit ' . $order['price']);
- }
- } else {
- // open order is gone, reset volume
- $trade->setVolumeExecuted(0);
- }
- return isset ($res['result']['open'][$txid]);
- }
- protected function sell(KrakenTradeSet &$trade, $bitcoins, $rate){
- $trade->setSellAt($rate);
- $this->transaction($trade, self::SELL, $bitcoins, $rate);
- }
- protected function buy(KrakenTradeSet &$trade, $bitcoins, $rate){
- $trade->setBuyRate($rate);
- $this->transaction($trade, self::BUY, $bitcoins, $rate);
- }
- /**
- * Place BUY or SELL order
- *
- * @param KrakenTradeSet $trade
- * @param string $action buy|sell
- * @param float $bitcoins
- * @param float $rate
- */
- protected function transaction(KrakenTradeSet &$trade, $action, $bitcoins, $rate){
- if (microtime(true) - $this->lastPrivateApiCallMicrotime <= 5){
- // sleep for 5 seconds
- // kraken does not allow fast orders :(
- usleep(5 * 1000000);
- }
- $this->lastPrivateApiCallMicrotime = microtime(true);
- $bitcoins = round($bitcoins, 10);
- $rate = round($rate, 5);
- $price = round($rate * $bitcoins, 5);
- $this->log('#' . $trade->getIndex() . ' ' . $action . ' ' . $bitcoins . ' XBTEUR @ limit ' . $rate . ' (' . $price . ')');
- $res = $this->QueryPrivate('AddOrder', array(
- 'pair' => 'XXBTZEUR',
- 'type' => $action,
- 'ordertype' => 'limit',
- 'price' => $rate,
- 'volume' => $bitcoins
- ));
- $this->apiLog($res);
- if (!isset($res['result']['txid'][0])){
- throw new KrakenTraderException('API response on ' . $action . ' is not valid. ' . print_r($res, true));
- }
- $txid = $res['result']['txid'][0];
- // assign the transaction ID to this trade
- $trade->setTxid($txid);
- $this->log('#' . $trade->getIndex() . ' made order ' . $txid);
- }
- protected function validateSet(){
- $sellRates = array();
- foreach($this->set as $trade){
- $sellRates[] = $trade->getBuyRate() + ($trade->getBuyRate() / 100 * $trade->getMargin());
- }
- foreach($sellRates as $sellRate){
- foreach($this->set as $trade){
- if ($sellRate <= $trade->getBuyRate()){
- throw new KrakenTraderException('buyRate + margin is lower then buyrate of another set. You would sell to yourself.');
- }
- }
- }
- }
- /**
- * Start trading
- *
- * @param int $timeout exit after X seconds
- */
- public function start($timeout = null){
- if (count($this->set) == 0){
- throw new KrakenTraderException('No sets available');
- }
- $this->validateSet();
- $start = time();
- while(true){
- if (!is_null($timeout) && time() > $start + $timeout){
- exit('Killed because of timeout (' . $timeout . ' seconds)');
- }
- $currentRate = $this->getCurrentRate();
- foreach($this->set as $index => &$trade){
- // foreach by reference
- if (!$trade->isProcessing()){
- // don't do anything with trades that are currently being processed
- if ($trade->getPlaceOrderAtAnyRate() !== true && $trade->getBuySell() == self::BUY && $currentRate <= $trade->getBuyRate()){
- // diff between current rate and buy rate
- $diff = abs($trade->getBuyRate() - $currentRate);
- // add 15% of the diff to the price
- $this->buy($trade, $trade->getBitcoins(), ($currentRate + ($diff * 0.15)));
- // set a flag
- $trade->setProcessing(true);
- }
- if ($trade->getPlaceOrderAtAnyRate() !== true && $trade->getBuySell() == self::SELL && $currentRate >= ($trade->getBuyRate() + ($trade->getBuyRate() / 100 * $trade->getMargin()))){
- // diff between current rate and sell rate
- $diff = abs($currentRate - ($trade->getBuyRate() + ($trade->getBuyRate() / 100 * $trade->getMargin())));
- // lower the price with 15% of the diff
- $this->sell($trade, $trade->getBitcoins(), ($currentRate - ($diff * 0.15)));
- // set a flag
- $trade->setProcessing(true);
- }
- // Any rate buying / selling
- if ($trade->getPlaceOrderAtAnyRate() === true){
- if ($trade->getBuySell() == self::BUY){
- $this->buy($trade, $trade->getBitcoins(), $trade->getBuyRate());
- // set a flag
- $trade->setProcessing(true);
- }
- if ($trade->getBuySell() == self::SELL){
- $this->sell($trade, $trade->getBitcoins(), ($trade->getBuyRate() + ($trade->getBuyRate() / 100 * $trade->getMargin())));
- // set a flag
- $trade->setProcessing(true);
- }
- }
- } else {
- // check if these orders are fullfilled
- if ($trade->getTxid() !== null){
- // if a transaction ID is set, the transaction must be made
- // check if it's still in the open Orders
- if(!$this->inOpenOrders($trade, $trade->getTxid())){
- // it's complete.
- if ($trade->getBuySell() == self::SELL){
- $logtext = 'sold';
- $atLimit = $trade->getSellAt();
- } else {
- $logtext = 'bought';
- $atLimit = $trade->getBuyRate();
- }
- $price = round($atLimit * $trade->getBitcoins(), 5);
- $this->log('#' . $trade->getIndex() . ' ' . $logtext . ' ' . $trade->getBitcoins() . ' XBTEUR @ limit ' . round($atLimit, 5) . ' (' . $price . ')');
- // logging
- if ($trade->getBuySell() == self::SELL){
- $trade->addSell();
- if ($trade->getNumberOfSells() > 0 && $trade->getNumberOfBuys() > 0){
- // calculate profit
- $feeOnBuy = $this->getFee($trade->getBuyRate() * $trade->getBitcoins());
- $feeOnSell = $this->getFee($trade->getSellAt() * $trade->getBitcoins());
- $totalFee = $feeOnBuy + $feeOnSell;
- $margin = ($trade->getSellAt() * $trade->getBitcoins()) - ($trade->getBuyRate() * $trade->getBitcoins());
- $profit = $margin - $totalFee;
- $this->log(array(
- 'index' => $trade->getIndex(),
- 'sells' => $trade->getNumberOfSells(),
- 'buys' => $trade->getNumberOfBuys(),
- 'margin' => round($margin, 5),
- 'fee' => round($totalFee, 5),
- 'profit' => round($profit, 5)
- ));
- // log profit totals
- $this->totalMargin += $margin;
- $this->totalFees += $totalFee;
- $this->totalProfit += $profit;
- $this->log(array(
- 'totalMargin' => round($this->totalMargin, 5),
- 'totalFee' => round($this->totalFees, 5),
- 'totalProfit' => round($this->totalProfit, 5)
- ));
- }
- // calculate new buy rate
- $newRate = $trade->getSellAt() / (1 + ($trade->getMargin() / 100));
- $trade->setBuyRate(round($newRate, 5));
- $this->log('#' . $trade->getIndex() . ' set new buy rate @ ' . $trade->getBuyRate());
- } else {
- $trade->addBuy();
- // log the new sell rate
- $this->log('#' . $trade->getIndex() . ' set new sell rate @ ' . round(($trade->getBuyRate() + ($trade->getBuyRate() / 100 * $trade->getMargin())), 5));
- }
- // remove the flag
- $trade->setTxid(null);
- $trade->setProcessing(false);
- // reverse buySell
- $buySell = ($trade->getBuySell() == self::SELL) ? self::BUY : self::SELL;
- $trade->setBuySell($buySell);
- }
- }
- }
- // sleep for 1 second in the total set
- usleep(1 / count($this->set) * 1000000);
- $this->objectLog(print_r($trade, true));
- }
- // remove reference
- unset($trade);
- // sleep for 1 second
- usleep(1 * 1000000);
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment