Skip to content

Commit

Permalink
Added files exchange.php and cryptopiaAPI.php
Browse files Browse the repository at this point in the history
Abstract class Exchange - general exchange API
class cryptopiaAPI - api wrapper for https://www.cryptopia.co.nz
  • Loading branch information
KittyCatTech committed Jan 31, 2017
1 parent 0ed3a91 commit 27a49b5
Show file tree
Hide file tree
Showing 2 changed files with 388 additions and 0 deletions.
224 changes: 224 additions & 0 deletions cryptopiaAPI.php
@@ -0,0 +1,224 @@
<?php
include 'exchange.php';

class Cryptopia extends Exchange{

public function __construct($priv, $pub) {
$this->privateKey = $priv;
$this->publicKey = $pub;

$result = json_decode($this->apiCall("GetBalance", array( 'Currency'=> 'BTC' )), true); // There is a bug in the API if you send no parameters it will return Success:true Error: Market not found.
// Array
// (
// [Success] => 1
// [Message] =>
// [Data] =>
// [Error] => Market not found.
// )
// print_r($result);
if( $result['Success'] != "true" ) {
throw new Exception("Can't Connect to Cryptopia, Error: " . $result['Error'] );
return false;
}
return true;
}

private function apiCall($method, array $req = array()) {
$public_set = array( "GetCurrencies", "GetTradePairs", "GetMarkets", "GetMarket", "GetMarketHistory", "GetMarketOrders" );
$private_set = array( "GetBalance", "GetDepositAddress", "GetOpenOrders", "GetTradeHistory", "GetTransactions", "SubmitTrade", "CancelTrade", "SubmitTip" );
static $ch = null;
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; Cryptopia.co.nz API PHP client; FreeBSD; PHP/'.phpversion().')');
if ( in_array( $method ,$public_set ) ) {
$url = "https://www.cryptopia.co.nz/api/" . $method;
if ($req) { foreach ($req as $r ) { $url = $url . '/' . $r; } }
curl_setopt($ch, CURLOPT_URL, $url );
} elseif ( in_array( $method, $private_set ) ) {
$url = "https://www.cryptopia.co.nz/Api/" . $method;
$nonce = explode(' ', microtime())[1];
$post_data = json_encode( $req );
$m = md5( $post_data, true );
$requestContentBase64String = base64_encode( $m );
$signature = $this->publicKey . "POST" . strtolower( urlencode( $url ) ) . $nonce . $requestContentBase64String;
$hmacsignature = base64_encode( hash_hmac("sha256", $signature, base64_decode( $this->privateKey ), true ) );
$header_value = "amx " . $this->publicKey . ":" . $hmacsignature . ":" . $nonce;
$headers = array("Content-Type: application/json; charset=utf-8", "Authorization: $header_value");
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_URL, $url );
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode( $req ) );
}
// run the query
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE); // Do Not Cache
$res = curl_exec($ch);
if ($res === false) throw new Exception('Could not get reply: '.curl_error($ch));
return $res;
}

// Some API calls require TradePairId rather than the TradePair so this should store the TradePairId
public function setSymbols() {
$result = json_decode($this->apiCall("GetTradePairs", array() ), true);

if( $result['Success'] == "true" ) {
$json = $result['Data'];
} else {
throw new Exception("Can't get symbols, Error: " . $result['Error'] );
}
foreach($json as $pair) {
// creates associative array of key: StandardSymbol (i.e. BTCUSD) value: ExchangeSymbol (i.e. btc_usd)
$this->symbols[ $this->makeStandardSymbol( $pair["Label"] ) ] = $pair["Id"];
}
}

public function updatePrices() {
$result = json_decode($this->apiCall("GetMarkets", array() ), true);
if( $result['Success'] == "true" ) {
$json = $result['Data'];
} else {
throw new Exception("Can't get markets, Error: " . $result['Error'] );
}
foreach($json as $pair) {
$this->prices[$pair['Label']]['high'] = $pair['High'];
$this->prices[$pair['Label']]['low'] = $pair['Low'];
$this->prices[$pair['Label']]['bid'] = $pair['BidPrice'];
$this->prices[$pair['Label']]['ask'] = $pair['AskPrice'];
$this->prices[$pair['Label']]['last'] = $pair['LastPrice'];
$this->prices[$pair['Label']]['time'] = ''; // not available on Cryptopia
}
}

// @todo add setBalance

Public function getBalance() {
$result = $this->apiCall("GetBalance", array('Currency'=> "") ); // "" for All currency balances
$result = json_decode($result, true);
if( $result['Success'] == "true" ) {
// @todo ADD CODE TO REFORMAT Array to standard
return $result['Data'];
} else {
throw new Exception("Can't get balances, Error: " . $result['Error'] );
}
}

Public function getCurrencyBalance( $currency ) {
$result = $this->apiCall("GetBalance", array( 'Currency'=> $currency ) );
$result = json_decode($result, true);
if( $result['Success'] == "true" ) {
return $result['Data'][0]['Total'];
} else {
throw new Exception("Can't get balance, Error: " . $result['Error'] );
}
}

// currency pair $symbol should be in standard Format not exchange format
public function activeOrders( $symbol = "")
{
if($symbol == "") {
$apiParams = array( 'TradePairId'=>"" );
} else {
$apiParams = array( 'TradePairId'=>$this->getExchangeSymbol($symbol) );
}
$myOrders = json_decode($this->apiCall("GetOpenOrders", $apiParams), true);
//print_r($myOrders);
// There is a bug in the API if you send no parameters it will return Success:true Error: Market not found.
// Array
// (
// [Success] => 1
// [Message] =>
// [Data] =>
// [Error] => Market not found.
// )

$orders = array();
$price = array(); // sort by price
if( $myOrders['Success'] == "true" && $myOrders['Error'] == "") {
foreach ($myOrders['Data'] as $order) {
$orderSymbol = $this->makeStandardSymbol($order["Market"]); // convert to standard format currency pair
$orders[] = ["symbol"=>$orderSymbol, "type"=>$order["Type"], "price"=>$order["Rate"],
"amount"=>$order["Remaining"], "id"=>$order["OrderId"] ];
if ($order["Type"] == "Sell") {
$price[] = 0 - $order['Rate']; // lowest ask price if first
} else {
$price[] = $order['Rate'];
}
}
if($orders) // If there are any orders
array_multisort($price, SORT_DESC, $orders); // sort orders by price
} else {
throw new Exception("Can't get active orders, Error: " . $myOrders['Error'] );
}
return $orders;
}

public function permissions() {

}

public function cancelOrder($id) {
$result = $this->apiCall("CancelTrade", array( 'Type'=>"Trade", 'OrderId'=>$id ));
$result = json_decode($result, true);
if( $result['Success'] == "true" ) {
echo "Orders Canceled: " . implode( ", ", $result['Data']) . "\n";
} else {
throw new Exception("Can't Cancel Order # $id, Error: " . $result['Error']);
}
}

public function cancelAll() {
if(!$this->activeOrders()) return false; // "No open orders to cancel.\n"
$result = $this->apiCall("CancelTrade", array( 'Type'=>"All" ));
$result = json_decode($result, true);
if( $result['Success'] == "true" ) {
return "Orders Canceled: " . implode( ", ", $result['Data']) . "\n";
} else {
throw new Exception("Can't Cancel All Orders, Error: " . $result['Error']);
}
}

public function orderStatus($id) {

}

public function placeOrder($symbol, $amount, $price, $side) {
$result = $this->apiCall("SubmitTrade", array( 'Type'=> $side, 'TradePairId'=> $this->getExchangeSymbol($symbol),
'Rate'=> number_format((float)$price, 8, '.', ''), 'Amount'=> number_format((float)$amount, 8, '.', '') ) );
$result = json_decode($result, true);
if( $result['Success'] == "true" ) {
return "Order Placed. OrderId:" .$result['Data']['OrderId'] .
" FilledOrders: " . implode( ", ", $result['Data']['FilledOrders']) . "\n";
} else {
throw new Exception("Can't Place Order, Error: " . $result['Error'] ); //*** die instead of echo
}
}

public function buy($symbol, $amount, $price) {
return $this->placeOrder($symbol, $amount, $price, 'Buy');
}

public function sell($symbol, $amount, $price) {
return $this->placeOrder($symbol, $amount, $price, 'Sell');
}

public function marketOrderbook($symbol)
{
$mktOrders = json_decode($this->apiCall("GetMarketOrders", array('TradePairId'=>$this->getExchangeSymbol($symbol))), true);
unset($orders);
if( $mktOrders['Success'] == "true" && $mktOrders['Error'] == "") {
//print_r($mktOrders);
foreach ($mktOrders['Data'] as $orderType => $order) {
foreach($order as $ordersByType) {
// $standardSymbol = $this->getStandardSymbol($symbol); // @todo not yet implemented
$orders[] = ["symbol"=>$symbol, "type"=>$orderType, "price"=>$ordersByType["Price"],
"amount"=>$ordersByType["Volume"] ];
}
}
} else {
throw new Exception("Can't get orderbook, Error: " . $mktOrders['Error'] );
}
return $orders;
}

}

?>
164 changes: 164 additions & 0 deletions exchange.php
@@ -0,0 +1,164 @@
<?php

abstract class Exchange
{
// ----- Variables -----

// Each exchanges class construction function should set these and test the connection to the API
private $privateKey = ''; // API Secret
private $publicKey = ''; // API Key

/**
* Symbols is an array of all the trading pairs on a given exchange.
*
* i.e. array("DOT/BTC", "LTC/BTC", "DOGE/BTC", "POT/BTC", "FTC/BTC", "WSX/BTC", "DARK/BTC", ...)
* It is assigned in the setSymbols() function and retrieved with the getSymbols() function.
* note: these can be different for different exchanges: "LTC/BTC" "LTCBTC" "ltc_btc"
* @todo Format functions or lookup tables need to be made for each exchange to standardize the format.
* getStandardSymbol($symbol) and getExchangeSymbol($symbol)
*
* @var array of strings
*/
protected $symbols = array(); //Trading Pairs

/**
* Prices are the current and 24hr market prices for each trading pair.
*
* Each trading pair has an array with keys: high, low, bid, ask, last, and time.
* i.e. {"ETHBTC": {"high": 0, "low": 0, "bid": 0, "ask": 0, "last": 0, "time": 0},
* ... ,
* "XMRBTC": {"high": 0, "low": 0, "bid": 0, "ask": 0, "last": 0, "time": 0}
* }
* @todo the updatePrices() function for each exchange should use makeStandardSymbol($exchangeSymbol) to get the key for each currency pair array
* note: trading pair symbol could be different but parameters these are the same across all exchanges
* It is assigned in the updatePrices() function and retrieved with the getPrices() function.
*
* @var array of arrays
*/
protected $prices = array();

/**
* Balances is an array of all currencies and assets.
*
* Each currency has an array with keys: amount, available, etc
* @todo Implement updateBalance() function to set the balance for each exchange.
* Should use makeStandardSymbol($exchangeSymbol) to get the key for each currency pair array
* @var array of arrays
*/
protected $balances = array();


// ----- Get and Set Functions -----

public function getSymbols() {
if(!$this->symbols) $this->setSymbols();
return $this->symbols;
}
// Can be used for currencies and currency pairs to put in only uppercase letters
// note: $exchangeSymbol must be have the base currency first and quote currency second (i.e. eth_btc Not btc_eth). The quote currency is usually BTC, USD, or CNY.
public function makeStandardSymbol($exchangeSymbol) {
// Convert to Uppercase and remove nonletters.
return preg_replace('/[^A-Z]/', '', strtoupper ( $exchangeSymbol ));
}
// @todo not implemented for all exchanges, setSymbols() must create associative array $this->symbols with keys as standard symbols and values as exchange symbols for this function to work.
public function getExchangeSymbol($standardSymbol) {

//if($standardSymbol == '') return ''; // used to get all symbols

if(!$this->symbols) $this->setSymbols();

if( isset($this->symbols[$standardSymbol]) ) {
return $this->symbols[$standardSymbol];
} else {
throw new Exception("Can't find symbol '" . $standardSymbol ."'" );
}
}

public function getPrices() {
return $this->prices;
}

// @todo the updateBalance() function for each exchange should use makeStandardSymbol($exchangeSymbol) to get the key for each currency pairs array
public function getBalance() { // this function is overridden until updateBalance() is implemented
// if(!isset($this.balances)) { updateBalance(); } // Not implemented for all exchanges yet
return $this->balances;
}
Public function getCurrencyBalance($currency) {
// @todo Update if not isset()
if(isset($this->balances[$curency]['amount'])) {
return $this->balances[$curency]['amount'];
}
}

// @todo not implemented for all exchanges, setSymbols() must create associative array $this->symbols with keys as standard symbols and values as exchange symbols for this function to work.
abstract public function setSymbols();

// abstract public function getFee($symbol); // @todo Not implemented for ct exchange yet

// @todo the updatePrices() function for each exchange should use makeStandardSymbol($exchangeSymbol) to get the key for each currency pairs array
abstract public function updatePrices();

// abstract public function updateBalance(); // Not implemented for all exchanges yet

// @todo ??? allow passing a $symbol for all exchanges?
abstract public function activeOrders();

abstract public function permissions();

abstract public function cancelOrder($id);
// is function should be overridden if exchange has cancel all function call
// @todo write generalized cancel all that loops through active orders, see btceAPI
abstract public function cancelAll();

abstract public function orderStatus($id);

// This should create a buy / sell order at the exchange for [amount] of [asset] at [price] and return the orderId
// @todo should implement getExchangeSymbol($standardSymbol) for each exchange
abstract public function buy($symbol, $amount, $price); // some exchanges may have additional parameters
abstract public function sell($symbol, $amount, $price); // some exchanges may have additional parameters


// ----- Data Calculation functions -----

// note: assumes buy offers are in descending order by price
public function highestBid($offers, $depth = 0, $type = "Buy") {
$total_amount = 0;
$highest_bid = 0;
if($offers) // if there are any offers
foreach( $offers as $item )
{
if( $item["type"] == $type ) {
//echo "type: " . $item["type"] . " price: " . $item['price'] . " amount: " .$item['amount'] . "\n";
$total_amount += floatval($item['amount']);

if( $highest_bid == 0 && $total_amount >= $depth ) // Go $1000 deep into the bids
{
$highest_bid = $item['price'];
break;
}
}
}
return $highest_bid;
}

// note: assumes sell offers are in accenting order by price
public function LowestAsk($offers, $depth = 0, $type = "Sell") {
return $this->highestBid($offers, $depth, $type);
}

// takes an array of of offers/orders and returns the volume within a price range
public function volume($offers, $lowerPriceLimit = 0, $upperPriceLimit = PHP_FLOAT_MAX) {
$total_vol = 0;
foreach( $offers as $item )
{
//echo "price: " . $item['Price'] . " amount: " .$item['Volume'] . "\n";
if( $item['price'] >= $lowerPriceLimit && $item['price'] <= $upperPriceLimit ) {
$total_vol += floatval($item['amount']);
}
}
return $total_vol;
}

} // end of class Exchange

?>

0 comments on commit 27a49b5

Please sign in to comment.