diff --git a/bot/CCXTAdapter.php b/bot/CCXTAdapter.php index 22a85e3..5d6a63a 100644 --- a/bot/CCXTAdapter.php +++ b/bot/CCXTAdapter.php @@ -36,9 +36,9 @@ abstract class CCXTAdapter extends Exchange { private $name = ''; protected $coinNames = [ ]; - private $tradeFees = [ ]; - private $precisions = [ ]; - private $limits = [ ]; + protected $tradeFees = [ ]; + protected $precisions = [ ]; + protected $limits = [ ]; private $lastStuckReportTime = [ ]; private $lastDuplicateWithdrawalTime = 0; @@ -393,7 +393,7 @@ public function refreshExchangeData() { continue; } - if ( !$market[ 'active' ] || !$this->isMarketActive( $market ) ) { + if ( !$this->isMarketActive( $market ) ) { continue; } diff --git a/bot/CoinManager.php b/bot/CoinManager.php deleted file mode 100644 index 1214ef1..0000000 --- a/bot/CoinManager.php +++ /dev/null @@ -1,968 +0,0 @@ -exchanges = &$exchanges; - - foreach ( $exchanges as $exchange ) { - $this->exchangesID[ $exchange->getID() ] = $exchange; - } - - } - - public function doManage( &$arbitrator ) { - - logg( "doManage()" ); - $this->stats = Database::getStats(); - - $this->checkKeys(); - - $stats = &$this->stats; - $hadAction = true; - - try { - // - if ( $stats[ self::STAT_NEXT_MANAGEMENT ] <= time() ) { - // - self::manageWallets( $arbitrator ); - $stats[ self::STAT_NEXT_MANAGEMENT ] = time() + Config::get( Config::INTERVAL_MANAGEMENT, Config::DEFAULT_INTERVAL_MANAGEMENT ) * 1800; - // - } - else if ( $stats[ self::STAT_NEXT_TAKE_PROFIT ] <= time() ) { - // - self::takeProfit(); - $stats[ self::STAT_NEXT_TAKE_PROFIT ] = time() + Config::get( Config::INTERVAL_TAKE_PROFIT, Config::DEFAULT_INTERVAL_TAKE_PROFIT ) * 3600; - // - } - else if ( $stats[ self::STAT_NEXT_STUCK_DETECTION ] <= time() ) { - // - self::stuckDetection(); - $stats[ self::STAT_NEXT_STUCK_DETECTION ] = time() + Config::get( Config::INTERVAL_STUCK_DETECTION, Config::DEFAULT_INTERVAL_STUCK_DETECTION ) * 3600; - // - } - else if ( $stats[ self::STAT_NEXT_DUPLICATE_DETECTION ] <= time() ) { - // - self::duplicateDetection(); - $stats[ self::STAT_NEXT_DUPLICATE_DETECTION ] = time() + Config::get( Config::INTERVAL_DUPLICATE_DETECTION, Config::DEFAULT_INTERVAL_DUPLICATE_DETECTION ) * 3600; - // - } - else if ( $stats[ self::STAT_NEXT_UNUSED_COIN_DETECTION ] <= time() ) { - // - self::unusedCoinsDetection(); - $stats[ self::STAT_NEXT_UNUSED_COIN_DETECTION ] = time() + Config::get( Config::INTERVAL_UNUSED_COIN_DETECTION, Config::DEFAULT_INTERVAL_UNUSED_COIN_DETECTION ) * 3600; - // - } - else if ( $stats[ self::STAT_NEXT_DB_CLEANUP ] <= time() ) { - // - self::dbCleanup(); - $stats[ self::STAT_NEXT_DB_CLEANUP ] = time() + Config::get( Config::INTERVAL_DB_CLEANUP, Config::DEFAULT_INTERVAL_DB_CLEANUP ) * 3600; - // - } - else { - $hadAction = false; - } - // - } - catch ( Exception $ex ) { - logg( "ERROR during management task: " . $ex->getMessage() . "\n" . $ex->getTraceAsString() ); - } - - Database::saveStats( $this->stats ); - - return $hadAction; - - } - - private function checkKeys() { - - $stats = &$this->stats; - - // load sensible defaults for stats... - if ( !key_exists( self::STAT_NEXT_MANAGEMENT, $stats ) ) { - $stats[ self::STAT_NEXT_MANAGEMENT ] = 0; - } - if ( !key_exists( self::STAT_NEXT_TAKE_PROFIT, $stats ) ) { - $stats[ self::STAT_NEXT_TAKE_PROFIT ] = time() + 48 * 3600; - } - if ( !key_exists( self::STAT_NEXT_STUCK_DETECTION, $stats ) ) { - $stats[ self::STAT_NEXT_STUCK_DETECTION ] = time() + 24 * 3600; - } - if ( !key_exists( self::STAT_NEXT_DUPLICATE_DETECTION, $stats ) ) { - $stats[ self::STAT_NEXT_DUPLICATE_DETECTION ] = time() + 24 * 3600; - } - if ( !key_exists( self::STAT_NEXT_UNUSED_COIN_DETECTION, $stats ) ) { - $stats[ self::STAT_NEXT_UNUSED_COIN_DETECTION ] = time() + 24 * 3600; - } - if ( !key_exists( self::STAT_NEXT_DB_CLEANUP, $stats ) ) { - $stats[ self::STAT_NEXT_DB_CLEANUP ] = time() + 7 * 24 * 3600; - } - if ( !key_exists( self::STAT_NEXT_CURRENCY_AGGRESSIVE_BALANCE_ALLOWED, $stats ) ) { - $stats[ self::STAT_NEXT_CURRENCY_AGGRESSIVE_BALANCE_ALLOWED ] = 0; - } - if ( !key_exists( self::STAT_BOT_AGE, $stats ) ) { - $stats[ self::STAT_BOT_AGE ] = time(); - } - if ( !key_exists( self::STAT_AUTOBUY_FUNDS, $stats ) ) { - $stats[ self::STAT_AUTOBUY_FUNDS ] = "0"; - } - - } - - private function dbCleanup() { - - logg( "dbCleanup()" ); - $rows = Database::cleanup(); - - logg( "Deleted $rows records" ); - - } - - private function saveSnapshot() { - - logg( "saveSnapshot()" ); - - $time = time(); - $balances = array( ); - foreach ( $this->exchanges as $exchange ) { - - $exid = $exchange->getID(); - $exname = $exchange->getName(); - - $wallets = $exchange->getWalletsConsideringPendingDeposits(); - $ticker = $exchange->getTickers( 'BTC' ); - - foreach ( $wallets as $coin => $value ) { - - $balance = formatBTC( $value ); - if ( isset( $balances[ $coin ] ) ) { - $balances[ $coin ] += $value; - } else { - $balances[ $coin ] = $value; - } - - if ( Config::isCurrency( $coin ) ) { - Database::saveSnapshot( $coin, $balance, $balance, 0, $exid, $time ); - continue; - } - - if ( !key_exists( $coin, $ticker ) ) { - logg( "$exname | Skipping $coin as it isn't traded against BTC" ); - if ( $value > 0 ) { - logg( "You have $balance $coin at $exname which cannot be traded against BTC!" ); - } - continue; - } - - $rate = formatBTC( $ticker[ $coin ] ); - - // Calculate desired balance: - $desiredBalance = 0; - $averageRate = 0; - $uses = Database::getOpportunityCount( $coin, 'BTC', $exid ); - // Desired balance can only raise above zero if the coin is required at this exchange - if ( $uses >= Config::get( Config::REQUIRED_OPPORTUNITIES, Config::DEFAULT_REQUIRED_OPPORTUNITIES ) ) { - - // Retrieve average exchange rates for this coin: - $averageRate = Database::getAverageRate( $coin ); - - $depositFee = abs( $exchange->getDepositFee( $coin, 1 ) * $averageRate ); - $withdrawFee = abs( $exchange->getWithdrawFee( $coin, 1 ) * $averageRate ); - $confTime = $exchange->getConfirmationTime( $coin ); - - if ($depositFee < Config::get( Config::MAX_TX_FEE_ALLOWED, Config::DEFAULT_MAX_TX_FEE_ALLOWED ) && - $withdrawFee < Config::get( Config::MAX_TX_FEE_ALLOWED, Config::DEFAULT_MAX_TX_FEE_ALLOWED ) && - $confTime < Config::get( Config::MAX_MIN_CONFIRMATIONS_ALLOWED, Config::DEFAULT_MAX_MIN_CONFIRMATIONS_ALLOWED ) ) { - $maxTradeSize = Config::get( Config::MAX_TRADE_SIZE, Config::DEFAULT_MAX_TRADE_SIZE ); - $balanceFactor = Config::get( Config::BALANCE_FACTOR, Config::DEFAULT_BALANCE_FACTOR ); - - $desiredBalance = formatBTC( $maxTradeSize / $averageRate * $balanceFactor ); - $diff = abs( $desiredBalance - $balance ); - // Only allow a diff if the need is fulfillable: - if ( $diff * $averageRate < $exchange->getSmallestOrderSize( $coin, 'BTC', 'buy' ) && - $diff * $averageRate < $exchange->getSmallestOrderSize( $coin, 'BTC', 'sell' ) ) { - $desiredBalance = $balance; - } - } - } - - logg( "$exname | $coin | AVAILABLE: $balance | RATE: $rate | AVG.RATE: $averageRate | USES: $uses | TARGET: $desiredBalance" ); - Database::saveSnapshot( $coin, $balance, $desiredBalance, $rate, $exid, $time ); - } - } - - $link = Database::connect(); - - foreach ( $balances as $coin => $balance ) { - Database::saveBalance( $coin, $balance, '0', $time, $link ); - } - - mysql_close( $link ); - - } - - private function balanceCurrencies() { - logg( "balanceCurrencies()" ); - $this->balance( 'BTC', true ); - - } - - private function balance( $coin, $skipUsageCheck = false, $safetyFactorArg = 0.01 ) { - - logg( "balance($coin)" ); - if ( Config::isBlocked( $coin ) ) { - logg( "Skipping: $coin is blocked!" ); - return; - } - - $exchanges = [ ]; - $exchangeWallets = [ ]; - foreach ( $this->exchanges as $exchange ) { - $wallets = $exchangeWallets[ $exchange->getName() ] = - $exchange->getWalletsConsideringPendingDeposits(); - if ( key_exists( $coin, $wallets ) ) { - $exchanges[] = $exchange; - } - } - - $requiredOpportunities = Config::get( Config::REQUIRED_OPPORTUNITIES, Config::DEFAULT_REQUIRED_OPPORTUNITIES ); - if ( $skipUsageCheck ) { - $requiredOpportunities = 0; - } - - $kickedExchanges = 0; - $totalCoins = 0; - foreach ( $exchanges as $exchange ) { - - $wallets = $exchangeWallets[ $exchange->getName() ]; - $balance = $wallets[ $coin ]; - $opportunityCount = Database::getOpportunityCount( $coin, 'BTC', $exchange->getID() ); - - logg( str_pad( $exchange->getName(), 10, ' ', STR_PAD_LEFT ) . ": $balance $coin ($opportunityCount usages)" ); - - $totalCoins += $balance; - - if ( $opportunityCount < $requiredOpportunities ) { - $kickedExchanges++; - continue; - } - } - - $count = count( $exchanges ) - $kickedExchanges; - - if ( $count == 0 ) { - logg( "Not balancing as this coin is unused!" ); - return; - } - - logg( " TOTAL: $totalCoins $coin" ); - $averageCoins = formatBTC( $totalCoins / $count ); - logg( " AVERAGE: $averageCoins $coin" ); - - $positiveExchanges = [ ]; - $negativeExchanges = [ ]; - - $minXFER = 0; - $safetyFactor = $safetyFactorArg; - if ( $coin == 'BTC' ) { - $minXFER = Config::get( Config::MIN_BTC_XFER, Config::DEFAULT_MIN_BTC_XFER ); - // Be a bit more conservative with BTC, since it's our profits after all! - $safetyFactor /= Config::get( Config::BTC_XFER_SAFETY_FACTOR, - Config::DEFAULT_BTC_XFER_SAFETY_FACTOR ); - } - $oneIsZero = false; - $oneIsNearZero = false; // Only used for BTC - $nearZeroThreshold = Config::get( Config::NEAR_ZERO_BTC_VALUE, Config::DEFAULT_NEAR_ZERO_BTC_VALUE ); - foreach ( $exchanges as $exchange ) { - // Allow max 1% of coin amount to be transfer fee: - $minXFER = max( $minXFER, $this->getSafeWithdrawFee( $exchange, $coin, $averageCoins ) / $safetyFactor ); - - $wallets = $exchange->getWallets(); - if ( $wallets[ $coin ] == 0 ) { - $oneIsZero = true; - } - if ( $coin == 'BTC' && $wallets[ $coin ] <= $nearZeroThreshold ) { - $oneIsNearZero = true; - } - } - logg( "XFER THRES.: $minXFER $coin" ); - - foreach ( $exchanges as $exchange ) { - - $opportunityCount = Database::getOpportunityCount( $coin, 'BTC', $exchange->getID() ); - - $wallets = $exchange->getWallets(); - $difference = formatBTC( $wallets[ $coin ] - ($opportunityCount < $requiredOpportunities ? 0 : $averageCoins) ); - - if ( $oneIsZero && $wallets[ $coin ] > 2 * $minXFER && - $difference == 0 ) { - // If the other exchange has run out of balance, send a minimum transfer to - // the other side to unblock it. - $difference = $minXFER; - } - - if ( abs( $difference ) < $minXFER ) { - logg( $exchange->getName() . " diff $difference $coin below xfer threshold!" ); - continue; - } - - if ( $difference < 0 ) { - logg( $exchange->getName() . " is missing $difference $coin" ); - $negativeExchanges[] = ['e' => $exchange, 'd' => abs( $difference ) ]; - } - else if ( $difference > 0 ) { - logg( $exchange->getName() . " could give $difference $coin" ); - $positiveExchanges[] = ['e' => $exchange, 'd' => $difference ]; - } - } - - if ( count( $positiveExchanges ) == 0 || count( $negativeExchanges ) == 0 ) { - if ( $oneIsNearZero && count( $exchanges ) > 2 ) { - // While balancing currencies with more than 2 exchanges, if we get to a situation - // where one of our exchanges is running out of its balance but we have been unable - // to perform a rebalancing, if we just give up we may end up stuck in this local - // minima for quite a while. So to avoid this, we try to be less conservative and - // relax our safety factor a bit to see if we'll manage to rebalance that way. - if ( $safetyFactorArg <= 0.09 && // Don't lose more than 10% of our balance in transfers! - // Throttle how often this feature kicks in. - time() >= $this->stats[ self::STAT_NEXT_CURRENCY_AGGRESSIVE_BALANCE_ALLOWED ] ) { - $this->stats[ self::STAT_NEXT_CURRENCY_AGGRESSIVE_BALANCE_ALLOWED ] = time() + - Config::get( Config::INTERVAL_CURRENCY_AGGRESSIVE_BALANCE, - Config::DEFAULT_INTERVAL_CURRENCY_AGGRESSIVE_BALANCE ) * 1800; - Database::saveStats( $this->stats ); - - logg( sprintf( "Failed to rebalance with a safety factor of %d%%, trying %d%% now...", - floor( $safetyFactorArg * 100 ), - floor( ( $safetyFactorArg + 0.01 ) * 100 ) ) ); - return $this->balance( $coin, $skipUsageCheck, $safetyFactorArg + 0.01 ); - } - } - logg( "No exchange in need or available to give" ); - return; - } - - while ( 'A' != 'B' ) { - - $from = -1; - $to = -1; - - for ( $i = 0; $i < count( $negativeExchanges ); $i++ ) { - $missing = $negativeExchanges[ $i ][ 'd' ]; - if ( $missing < $minXFER ) { - continue; - } - $to = $i; - - for ( $j = 0; $j < count( $positiveExchanges ); $j++ ) { - $offering = $positiveExchanges[ $j ][ 'd' ]; - if ( $offering < $minXFER ) { - continue; - } - $from = $j; - - break; - } - - break; - } - - if ( $from < 0 || $to < 0 ) { - break; - } - - $amount = min( $positiveExchanges[ $from ][ 'd' ], $negativeExchanges[ $to ][ 'd' ] ); - $source = $positiveExchanges[ $from ][ 'e' ]; - $target = $negativeExchanges[ $to ][ 'e' ]; - - $this->withdraw( $source, $target, $coin, $amount ); - - $positiveExchanges[ $from ][ 'd' ] -= $amount; - $negativeExchanges[ $to ][ 'd' ] -= $amount; - - // - } - - } - - private function stuckDetection() { - - if ( !Config::get( Config::MODULE_STUCK_DETECTION, Config::DEFAULT_MODULE_STUCK_DETECTION ) ) { - return; - } - - logg( "stuckDetection()" ); - foreach ( $this->exchanges as $exchange ) { - $exchange->detectStuckTransfers(); - } - - } - - private function duplicateDetection() { - - if ( !Config::get( Config::MODULE_DUPLICATE_DETECTION, Config::DEFAULT_MODULE_DUPLICATE_DETECTION ) ) { - return; - } - - logg( "duplicateDetection()" ); - foreach ( $this->exchanges as $exchange ) { - $exchange->detectDuplicateWithdrawals(); - } - - } - - private $unusedCoins = [ ]; - - private function unusedCoinsDetection() { - - if ( !Config::get( Config::MODULE_UNUSED_COINS_DETECTION, Config::DEFAULT_MODULE_UNUSED_COINS_DETECTION ) ) { - return; - } - - logg( "unusedCoinsDetection()" ); - foreach ( $this->exchanges as $exchange ) { - - $xid = $exchange->getID(); - $ticker = $exchange->getTickers( 'BTC' ); - $wallets = $exchange->getWalletsConsideringPendingDeposits(); - - foreach ( $wallets as $coin => $balance ) { - - if ( !key_exists( $xid, $this->unusedCoins ) || !key_exists( $coin, $this->unusedCoins[ $xid ] ) ) { - logg( "[DEBUG] $xid $coin => init, set 0" ); - $this->unusedCoins[ $xid ][ $coin ] = 0; - } - - if ( $balance == 0 ) { - logg( "[DEBUG] $xid $coin => zero balance, set 0" ); - $this->unusedCoins[ $xid ][ $coin ] = 0; - continue; - } - - if ( Config::isCurrency( $coin ) ) { - logg( "[DEBUG] $xid $coin => is currency, set 0" ); - $this->unusedCoins[ $xid ][ $coin ] = 0; - continue; - } - - if ( key_exists( $coin, $ticker ) && !Config::isBlocked( $coin ) ) { - logg( "[DEBUG] $xid $coin => not blocked and traded, set 0" ); - $this->unusedCoins[ $xid ][ $coin ] = 0; - continue; - } - - if ( $this->unusedCoins[ $xid ][ $coin ] == 0 ) { - logg( "Queueing $coin @ " . $exchange->getName() . " for liquidation notification..." ); - $this->unusedCoins[ $xid ][ $coin ] = time(); - continue; - } - - if ( time() > $this->unusedCoins[ $xid ][ $coin ] + 3600 * 24 ) { - $this->unusedCoins[ $xid ][ $coin ] = 0; - logg( "Unused $balance $coin @ " . $exchange->getName() . ". It is safe to liquidate this coin!", true ); - } - } - } - - } - - private function takeProfit() { - - if ( !Config::get( Config::MODULE_TAKE_PROFIT, Config::DEFAULT_MODULE_TAKE_PROFIT ) ) { - return; - } - - logg( "takeProfit()" ); - - $profitAddress = Config::get( Config::TAKE_PROFIT_ADDRESS, Config::DEFAULT_TAKE_PROFIT_ADDRESS ); - $profitLimit = Config::get( Config::TAKE_PROFIT_AMOUNT, Config::DEFAULT_TAKE_PROFIT_AMOUNT ); - - if ( is_null( $profitAddress ) || is_null( $profitLimit ) || strlen( $profitAddress ) < 34 ) { - logg( "Skipping: Configuration is incomplete/invalid" ); - return; - } - - $totalBTC = 0; - - $highestExchange = null; - $highestExchangeAmount = 0; - - foreach ( $this->exchanges as $exchange ) { - $balance = $exchange->getWallets()[ 'BTC' ]; - - if ( $balance > $highestExchangeAmount ) { - $highestExchangeAmount = $balance; - $highestExchange = $exchange; - } - - $totalBTC += $balance; - } - - $averageBTC = formatBTC( $totalBTC / count( $this->exchanges ) ); - - $profit = formatBTC( $totalBTC - $profitLimit ); - logg( "Profit: " . $profit ); - $restockCash = formatBTC( min( Config::get( Config::TAKE_PROFIT_MIN_RESTOCK_CASH, - Config::DEFAULT_TAKE_PROFIT_MIN_RESTOCK_CASH ), - $profit * Config::get( Config::TAKE_PROFIT_RESTOCK_CASH_PERCENTAGE, - Config::DEFAULT_TAKE_PROFIT_RESTOCK_CASH_PERCENTAGE ) ) ); - - $minXFER = Config::get( Config::MIN_BTC_XFER, Config::DEFAULT_MIN_BTC_XFER ); - // Be a bit more conservative with BTC, since it's our profits after all! - $safetyFactor = 0.01 / Config::get( Config::BTC_XFER_SAFETY_FACTOR, - Config::DEFAULT_BTC_XFER_SAFETY_FACTOR ); - foreach ( $this->exchanges as $exchange ) { - // Allow max 1%/safetyFactor of coin amount to be transfer fee: - $minXFER = max( $minXFER, $this->getSafeWithdrawFee( $exchange, 'BTC', $averageBTC ) / $safetyFactor ); - } - - $remainingProfit = formatBTC( $profit - $restockCash ); - if ( $remainingProfit < $minXFER ) { - logg( "Not enough profit yet..." ); - return; - } - - logg( "Withdrawing profit: $remainingProfit BTC to $profitAddress", true ); - if ( $highestExchange->withdraw( 'BTC', $remainingProfit, $profitAddress ) ) { - $txFee = $this->getSafeWithdrawFee( $highestExchange, 'BTC', $averageBTC ); - Database::recordProfit( $remainingProfit - $txFee, 'BTC', $profitAddress, time() ); - Database::saveWithdrawal( 'BTC', $remainingProfit, $profitAddress, $highestExchange->getID(), 0, - $highestExchange->getWithdrawFee( 'BTC', $remainingProfit ) ); - - // ------------------------------------------------------------------------- - $restockFunds = $this->stats[ self::STAT_AUTOBUY_FUNDS ]; - logg( "Overwriting restock funds..." ); - logg( "Restock cash before: $restockFunds BTC" ); - $this->stats[ self::STAT_AUTOBUY_FUNDS ] = $restockCash; - logg( " Restock cash after: $restockCash BTC" ); - logg( " Remaining profit: $remainingProfit BTC" ); - // ------------------------------------------------------------------------- - } - - } - - private function autobuyAltcoins( &$arbitrator ) { - - if ( !Config::get( Config::MODULE_AUTOBUY, Config::DEFAULT_MODULE_AUTOBUY ) ) { - return; - } - - logg( "autobuyAltcoins()" ); - - $autobuyFunds = formatBTC( $this->stats[ self::STAT_AUTOBUY_FUNDS ] ); - logg( "Autobuy funds: $autobuyFunds BTC" ); - - $autobuyAmount = formatBTC( min( Config::get( Config::MAX_BUY, Config::DEFAULT_MAX_BUY ), $autobuyFunds ) ); - logg( "Autobuying for $autobuyAmount BTC" ); - if ( $autobuyAmount < 0.0001 ) { - logg( "Not enough autobuy funds" ); - $this->stats[ self::STAT_AUTOBUY_FUNDS ] = 0; - return; - } - - $needs = [ ]; - $allWallets = [ ]; - foreach ( $this->exchanges as $exchange ) { - $allWallets[ $exchange->getID() ] = $exchange->getWalletsConsideringPendingDeposits(); - } - - logg( "Getting need..." ); - - $results = Database::getCurrentSimulatedProfitRate(); - $stats = Database::getWalletStats(); - foreach ( $results as $row ) { - - $currency = $row[ 'currency' ]; - $coin = $row[ 'coin' ]; - if ( !Config::isCurrency( $currency ) ) { - logg( "Skipping: $currency is not a currency!" ); - continue; - } - if ( Config::isCurrency( $coin ) || Config::isBlocked( $coin ) ) { - logg( "Skipping: $coin is blocked!" ); - continue; - } - - $exchangeID = $row[ 'ID_exchange_target']; - $stat = $stats[ $coin ][ $exchangeID ]; - $exchange = $this->exchangesID[ $exchangeID ]; - $balance = $allWallets[ $exchangeID ][ $coin ]; - $desiredBalance = $stat[ 'desired_balance' ]; - - $diff = formatBTC( $desiredBalance - $balance ); - if ( $diff > 0 ) { - logg( "Need $diff $coin @ " . $exchange->getName() ); - // We add an entry to the $needs array ratio times to get a weighted randomized autobuy function. - for ( $i = 0; $i < $row[ 'ratio' ]; $i++ ) { - $needs[] = ['coin' => $coin, 'amount' => $diff, 'exchange' => $exchangeID ]; - } - } - } - - if ( count( $needs ) == 0 ) { - logg( "No need!" ); - return; - } - - // Try up to ten times to fill a need - for ( $i = 0; $i < 10; $i++ ) { - - logg( "Rolling the dice..." ); - shuffle( $needs ); - $need = $needs[ 0 ]; - - $exchange = $this->exchangesID[ $need[ 'exchange' ] ]; - $coin = $need[ 'coin' ]; - $needAmount = $need[ 'amount' ]; - - logg( "Filling need $needAmount $coin @ " . $exchange->getName() ); - - $orderbook = $exchange->getOrderbook( $coin, 'BTC' ); - if ( is_null( $orderbook ) ) { - logg( "Invalid orderbook!" ); - continue; - } - $rate = formatBTC( $orderbook->getBestAsk()->getPrice() ); - $askAmount = $orderbook->getBestAsk()->getAmount(); - $buyAmount = formatBTC( min( $needAmount, min( $askAmount, $autobuyAmount / ($rate * 1.01) ) ) ); - $buyPrice = formatBTC( $buyAmount * $rate ); - if ( $buyPrice < $exchange->getSmallestOrderSize( $coin, 'BTC', 'buy' ) ) { - logg( "Not enough coins at top of the orderbook!" ); - continue; - } - - $tradeableBefore = $exchange->getWallets()[ $coin ]; - logg( "Posting buy order to " . $exchange->getName() . ": $buyAmount $coin for $buyPrice @ $rate" ); - $orderID = $exchange->buy( $coin, 'BTC', $rate, $buyAmount ); - if ( !is_null( $orderID ) ) { - logg( "Waiting for order execution..." ); - sleep( Config::get( Config::ORDER_CHECK_DELAY, Config::DEFAULT_ORDER_CHECK_DELAY ) ); - - if ( !$exchange->cancelOrder( $orderID ) ) { - // Cancellation failed: Order has been executed! - logg( "Order executed!" ); - Database::saveManagement( $coin, $buyAmount, $rate, $exchange->getID() ); - $this->stats[ self::STAT_AUTOBUY_FUNDS ] = formatBTC( $autobuyFunds - $buyPrice ); - - // Make sure the wallets are updated for pending deposit calculations. - $tradesMade = array( - $exchange->getID() => array( - $tradeable => $buyAmount, - ) - ); - $exchange->refreshWallets( $tradesMade ); - - $arbitrator->getTradeMatcher()->handlePostTradeTasks( $arbitrator, $exchange, $coin, 'BTC', 'buy', - $orderID, $buyAmount ); - return; - } - } - } - - } - - private function manageWallets( &$arbitrator ) { - - logg( "manageWallets()" ); - - self::saveSnapshot(); - - self::autobuyAltcoins( $arbitrator ); - - self::balanceCurrencies(); - - self::balanceAltcoins(); - - self::liquidateAltcoins( $arbitrator ); - - } - - private function balanceAltcoins() { - - logg( "balanceAltcoins()" ); - - if ( !Config::get( Config::MODULE_AUTOBALANCE, Config::DEFAULT_MODULE_AUTOBALANCE ) ) { - logg( "Module disabled: Skipping balancing!" ); - return; - } - - $allcoins = [ ]; - foreach ( $this->exchanges as $exchange ) { - $wallets = $exchange->getWalletsConsideringPendingDeposits(); - foreach ( $wallets as $coin => $balance ) { - if ( $balance > 0 ) { - $allcoins[] = $coin; - } - } - } - - $coins = array_unique( $allcoins ); - foreach ( $coins as $coin ) { - if ( $coin == 'BTC' ) { - continue; - } - $this->balance( $coin ); - } - - } - - private function liquidateAltcoins( &$arbitrator ) { - logg( "liquidateAltcoins()" ); - - if ( !Config::get( Config::MODULE_LIQUIDATE, Config::DEFAULT_MODULE_LIQUIDATE ) ) { - logg( "Module disabled: Skipping liquidation!" ); - return; - } - - if ( $this->getBotAgeInDays() < 7 ) { - logg( "Bot not active long enough: Skipping liquidation!" ); - return; - } - - logg( "Calculating overbalances..." ); - $overbalances = [ ]; - - $stats = Database::getWalletStats(); - foreach ( $stats as $coin => $data ) { - - if ( Config::isCurrency( $coin ) || Config::isBlocked( $coin ) ) { - logg( "Skipping: $coin is blocked!" ); - continue; - } - - $entry = [ ]; - - $coinIsNeeded = false; - foreach ( $data as $exchangeID => $stat ) { - - $exchange = $this->exchangesID[ $exchangeID ]; - $wallets = $exchange->getWallets(); - $balance = $wallets[ $coin ]; - $desiredBalance = $stat[ 'desired_balance' ]; - - $diff = formatBTC( $desiredBalance - $balance ); - if ( $diff == 0 ) { - continue; - } - - if ( $diff > 0 ) { - logg( "Need $diff $coin @ " . $exchange->getName() ); - $coinIsNeeded = true; - continue; - } - - $aDiff = abs( $diff ); - - logg( "Unneeded $aDiff $coin @ " . $exchange->getName() ); - $entry[] = ['coin' => $coin, 'amount' => $aDiff, 'exchange' => $exchangeID ]; - } - - if ( $coinIsNeeded ) { - logg( "$coin is needed: Not liquidating!" ); - continue; - } - - $overbalances = array_merge( $entry, $overbalances ); - } - - if ( count( $overbalances ) == 0 ) { - logg( "No overbalances!" ); - return; - } - - foreach ( $overbalances as $overbalance ) { - - $exchange = $this->exchangesID[ $overbalance[ 'exchange' ] ]; - $coin = $overbalance[ 'coin' ]; - $liquidationAmount = $overbalance[ 'amount' ]; - - logg( "Liquidating $liquidationAmount $coin @ " . $exchange->getName() ); - - $orderbook = $exchange->getOrderbook( $coin, 'BTC' ); - if ( is_null( $orderbook ) ) { - logg( "Invalid orderbook!" ); - continue; - } - $rate = formatBTC( $orderbook->getBestBid()->getPrice() ); - $bidAmount = $orderbook->getBestBid()->getAmount(); - $sellAmount = formatBTC( min( $liquidationAmount, $bidAmount ) ); - $sellPrice = formatBTC( $sellAmount * $rate ); - if ( $sellPrice < $exchange->getSmallestOrderSize( $coin, 'BTC', 'sell' ) ) { - logg( "Not enough coins at top of the orderbook!" ); - continue; - } - - $tradeableBefore = $exchange->getWallets()[ $coin ]; - logg( "Posting sell order to " . $exchange->getName() . ": $sellAmount $coin for $sellPrice @ $rate" ); - $orderID = $exchange->sell( $coin, 'BTC', $rate, $sellAmount ); - if ( !is_null( $orderID ) ) { - logg( "Waiting for order execution..." ); - sleep( Config::get( Config::ORDER_CHECK_DELAY, Config::DEFAULT_ORDER_CHECK_DELAY ) ); - - if ( !$exchange->cancelOrder( $orderID ) ) { - // Cancellation failed: Order has been executed! - logg( "Order executed!" ); - Database::saveManagement( $coin, $sellAmount * -1, $rate, $exchange->getID() ); - - // Make sure the wallets are updated for pending deposit calculations. - $tradesMade = array( - $exchange->getID() => array( - $coin => -$sellAmount, - ) - ); - $exchange->refreshWallets( $tradesMade ); - - $arbitrator->getTradeMatcher()->handlePostTradeTasks( $arbitrator, $exchange, $coin, 'BTC', 'sell', - $orderID, $sellAmount ); - } - } - } - - } - - public function getBotAgeInDays() { - return floor( $this->getBotAgeInSeconds() / 24 * 3600 ); - - } - - public function getBotAgeInSeconds() { - return time() - $this->stats[ self::STAT_BOT_AGE ]; - - } - - private function doWithdraw( $source, $coin, $amount, $address, $tag ) { - - try { - return $source->withdraw( $coin, $amount, $address, $tag ); - } - catch ( Exception $ex ) { - // Perhaps the withdrawal was unsuccessful because of insufficient balance. - // This can happen if the account only has $amount balance, in which case - // we need to subtract the withdrawal fee. - $amount -= $source->getWithdrawFee( $coin, $amount ); - return $source->withdraw( $coin, $amount, $address, $tag ); - } - - return false; - - } - - public function withdraw( $source, $target, $coin, $amount ) { - - if ( !Config::isCurrency( $coin ) ) { - $limits = $source->getWithdrawLimits( $coin, 'BTC' ); - - if ( !is_null( $limits[ 'amount' ][ 'min' ] ) && - floatval( $limits[ 'amount' ][ 'min' ] ) > $amount ) { - logg( sprintf( "Withdrawal amount %s below minimum trade amount %s", - $amount, $limits[ 'amount' ][ 'min' ] ) ); - return false; - } - - if ( !is_null( $limits[ 'amount' ][ 'max' ] ) && - floatval( $limits[ 'amount' ][ 'max' ] ) < $amount ) { - logg( sprintf( "Withdrawal amount %s above maximum trade amount %s", - $amount, $limits[ 'amount' ][ 'max' ] ) ); - return false; - } - } - - $amount = formatBTC( $amount ); - logg( "Transfering $amount $coin " . $source->getName() . " => " . $target->getName() ); - $address = $target->getDepositAddress( $coin ); - $tag = null; - if ( is_array( $address ) ) { - $tag = $address[ 1 ]; - $address = $address[ 0 ]; - } - if ( is_null( $address ) || strlen( trim( $address ) ) == 0 ) { - logg( "Invalid deposit address for " . $target->getName() . ", received: ". $address ); - - return false; - } - - - logg( "Deposit Address: $address, Memo: " . ( is_null( $tag ) ? "null" : $tag ) ); - if ( $this->doWithdraw( $source, $coin, $amount, trim( $address ), $tag ) ) { - Database::saveWithdrawal( $coin, $amount, trim( $address ), $source->getID(), $target->getID(), - $source->getWithdrawFee( $coin, $amount ) + - $target->getDepositFee( $coin, $amount ) ); - - return true; - } - - return false; - - } - - public function getSafeDepositFee( $exchange, $tradeable, $amount ) { - - $fee = $exchange->getDepositFee( $tradeable, $amount ); - if ( is_null( $fee ) ) { - $fee = 0; - } - - return $fee; - - } - - public function getSafeWithdrawFee( $exchange, $tradeable, $amount ) { - - $fee = $exchange->getWithdrawFee( $tradeable, $amount ); - if ( !is_null( $fee ) ) { - return $fee; - } - - $txFees = [ ]; - - // Average fee from other exchanges: - foreach ( $this->exchanges as $x ) { - - if ( $x === $exchange ) { - continue; - } - - $txFee = $x->getWithdrawFee( $tradeable, $amount ); - if ( !is_null( $txFee ) ) { - $txFees[] = $txFee; - } - } - - if ( count( $txFees ) == 0 ) { - logg( "[" . $exchange->getName() . "] WARNING: Unknown transfer fee for $tradeable. Calculation may be inaccurate!" ); - return 0; - } - - return max( $txFees ); - - } - -} diff --git a/bot/xchange/Binance.php b/bot/xchange/Binance.php index 557e2de..914841f 100644 --- a/bot/xchange/Binance.php +++ b/bot/xchange/Binance.php @@ -47,7 +47,7 @@ public function getRateLimit() { } public function isMarketActive( $market ) { - return $market[ 'info' ][ 'status' ] == 'TRADING'; + return $market[ 'active' ] || $market[ 'info' ][ 'status' ] == 'TRADING'; } public function checkAPIReturnValue( $result ) { diff --git a/bot/xchange/Cryptopia.php b/bot/xchange/Cryptopia.php new file mode 100644 index 0000000..e5c321f --- /dev/null +++ b/bot/xchange/Cryptopia.php @@ -0,0 +1,273 @@ +exchange->rateLimit; + return $limit; + } + + public function isMarketActive( $market ) { + return $market[ 'active' ] && $market[ 'info' ][ 'Status' ] == 'OK'; + } + + public function checkAPIReturnValue( $result ) { +// if ( !$result[ 'info' ][ 'Error' ] ) { +// return false; +// } + return $result[ 'info' ][ 'Success' ] === true; + } + + public function getDepositHistory() { + + $history = [ ]; + + $result = $this->exchange->privatePostGetTransactions( [ 'Type' => 'Deposit' ] ); + + if($result[ 'Success' ]) { + $history = $result[ 'Data' ]; + } + + return array( + + 'history' => $history, + 'statusKey' => 'Status', + 'coinKey' => 'Currency', + 'amountKey' => 'Amount', + 'timeKey' => 'TimeStamp', + 'pending' => 'Pending', + 'completed' => 'Success', + + ); + + } + + public function getWithdrawalHistory() { + + $history = [ ]; + + $result = $this->exchange->privatePostGetTransactions( [ 'Type' => 'Whitdraw' ] ); + + if($result[ 'Success' ]) { + $history = $result[ 'Data' ]; + } + + return array( + + 'history' => $history, + 'statusKey' => 'Status', + 'coinKey' => 'Currency', + 'amountKey' => 'Amount', + 'timeKey' => 'TimeStamp', + 'txidKey' => 'TxId', + 'addressKey' => 'Address', + 'pending' => 'Pending', + 'completed' => 'Success', + + ); + + } + + // Changed functions! + + public function refreshExchangeData() { + + $pairs = [ ]; + $precisions = [ ]; + $limits = [ ]; + $tradeFees = [ ]; + $conf = [ ]; + $fees = [ ]; + $markets = $this->exchange->loadMarkets(); + + $currencies = $this->exchange->fetch_currencies(); + + foreach ( $markets as $market ) { + + $tradeable = $market[ 'base' ]; + $currency = $market[ 'quote' ]; + + if ( !Config::isCurrency( $currency ) || + Config::isBlocked( $tradeable ) ) { + continue; + } + + if ( !$this->isMarketActive( $market ) ) { + continue; + } + + if ( !key_exists( $tradeable, $currencies ) || !$currencies[ $tradeable ] ) { + logg( $this->prefix() . "Coin '$tradeable' is disabled by Exchange!", true ); + continue; + } + + $this->coinNames[ strtoupper( $tradeable ) ] = $tradeable; + $this->coinNames[ strtoupper( $currency ) ] = $currency; + + $pair = strtoupper( $tradeable . "_" . $currency ); + $pairs[] = $pair; + $precisions[ $pair ] = $market[ 'precision' ]; + $limits[ $pair ] = $market[ 'limits' ]; + // We can't be sure whether we'll be the maker or the taker in each trade, so we'll assume the worst. + $tradeFees[ $pair ] = max( $market[ 'maker' ], $market[ 'taker' ] ); + $fees[ $tradeable ] = $currencies[ $tradeable ][ 'fee' ]; + $conf[ $tradeable ] = 0; // unknown! + } + + $this->pairs = $pairs; + $this->confirmationTimes = $conf; + $this->tradeFees = $tradeFees; + $this->precisions = $precisions; + $this->limits = $limits; + $this->withdrawFees = $fees; + $this->depositFees = $fees; + + $this->calculateTradeablePairs(); + + } + + public function getTickers( $currency ) { + + $currency = $this->coinNames[ $currency ]; + $markets = $this->exchange->loadMarkets(); + + $ticker = [ ]; + foreach ( $markets as $market ) { + if ( $market[ 'quote' ] != $currency ) { + continue; + } + + $ticker[] = $market[ 'symbol' ]; + } + + $tickers = $this->exchange->fetchTickers( $ticker ); + $ticker = [ ]; + foreach ( $tickers as $row ) { + $split = explode( '/', $row[ 'symbol' ] ); + + // The API doesn't provide the last price, so we just take an average into our spread. :-( + $ticker[ strtoupper( $split[ 0 ] ) ] = formatBTC( $row[ 'last' ] ); + } + + return $ticker; + + } + + public function withdraw( $coin, $amount, $address, $tag = null ) { + + $coin = $this->coinNames[ $coin ]; + if ( floatval( $amount ) === 0 ) { + // Simulate an error which ccxt may not raise for us + return false; + } + + return $this->checkAPIReturnValue( $this->exchange->withdraw( $coin, formatBTC( $amount ), $address, $tag ) ); + + } + + public function getDepositAddress( $coin ) { + + $coin = $this->coinNames[ $coin ]; + $currencies = $this->exchange->fetch_currencies(); + + if ( !key_exists( $coin, $currencies ) || !$currencies[ $coin ][ 'active' ] ) { + logg( $this->prefix() . "Coin '$coin' is disabled by Exchange!", true ); + return null; + } + + $result = $this->exchange->fetch_deposit_address( $coin ); + if ( !isset( $result[ 'address' ] ) ) { + logg( $this->prefix() . "Please generate a deposit address for $coin!", true ); + return null; + } + if ( key_exists( 'tag', $result ) && !is_null( $result[ 'tag' ] ) ) { + return array( $result[ 'address' ], $result[ 'tag' ] ); + } + return $result[ 'address' ]; + + } + + public function buy( $tradeable, $currency, $rate, $amount ) { + try { + $precisions = $this->getPrecision( $tradeable, $currency ); + $limits = $this->getLimits( $tradeable, $currency ); + $fee = $this->tradeFees[ $tradeable . "_" . $currency ]; + if($limits[ 'amount' ][ 'min' ] > $amount){ + logg( $this->prefix() . "Amount for $tradeable less MIN in buy()!" ); + return null; + } +// $amount_fee = round($amount * $fee, $precisions[ 'amount' ]); + $amount_fee = $amount * $fee; + print( $this->prefix() . "INFO BUY [$tradeable]:\n" ); + print( $this->prefix() . "Limits = " . $limits[ 'amount' ][ 'min' ] . " Precisions = " . $precisions[ 'amount' ] . "\n" ); + print( $this->prefix() . "Amount = " . $amount . "\n" ); + print( $this->prefix() . "Fee = " . $fee . " => " . $amount_fee . "\n" ); +// $amount = round($amount + $amount_fee, $precisions[ 'amount' ]); + $amount = $amount + $amount_fee; + print( $this->prefix() . "Total = " . $amount . "\n" ); + + $result = $this->exchange->create_order( $tradeable . '/' . $currency, 'limit', 'buy', + $amount, $rate ); + return $currency . '_' . $tradeable . ':' . $result[ 'id' ]; + } + catch ( Exception $ex ) { + logg( $this->prefix() . "Got an exception in buy(): " . $ex->getMessage() ); + return null; + } + } + + public function sell( $tradeable, $currency, $rate, $amount ) { + try { + $precisions = $this->getPrecision( $tradeable, $currency ); + $limits = $this->getLimits( $tradeable, $currency ); + $fee = $this->tradeFees[ $tradeable . "_" . $currency ]; + if($limits[ 'amount' ][ 'min' ] > $amount){ + logg( $this->prefix() . "Amount for $tradeable less MIN in sell() [" . $limits[ 'amount' ][ 'min' ] . "]!" ); + return null; + } +// $amount_fee = round($amount * $fee, $precisions[ 'amount' ]); + $amount_fee = $amount * $fee; + print( $this->prefix() . "INFO SELL [$tradeable]:\n" ); + print( $this->prefix() . "Limits = " . $limits[ 'amount' ][ 'min' ] . " Precisions = " . $precisions[ 'amount' ] . "\n" ); + print( $this->prefix() . "Amount = " . $amount . "\n" ); + print( $this->prefix() . "Fee = " . $fee . " => " . $amount_fee . "\n" ); +// $amount = round($amount - $amount_fee, $precisions[ 'amount' ]); + $amount = $amount - $amount_fee; + print( $this->prefix() . "Total = " . $amount . "\n" ); + + $result = $this->exchange->create_order( $tradeable . '/' . $currency, 'limit', 'sell', + $amount, $rate ); + return $currency . '_' . $tradeable . ':' . $result[ 'id' ]; + } + catch ( Exception $ex ) { + logg( $this->prefix() . "Got an exception in sell(): " . $ex->getMessage() ); + return null; + } + } + + public function testAccess_() { + $this->refreshExchangeData(); +// $markets = $this->exchange->loadMarkets(); +// var_dump($this->getDepositAddress( 'OMG' ));die(); + var_dump($this->exchange->fetch_currencies()['OMG']);die(); +// var_dump($markets['REP/BTC']);die(); + } + +}; diff --git a/bot/xchange/Hitbtc.php b/bot/xchange/Hitbtc.php new file mode 100644 index 0000000..aea783c --- /dev/null +++ b/bot/xchange/Hitbtc.php @@ -0,0 +1,436 @@ +exchange->rateLimit; + return $limit; + } + + public function isMarketActive( $market ) { + + $tradeable = $market[ 'base' ]; + + if ( !$this->exchange->currencies[ $tradeable ][ 'payout' ] || !$this->exchange->currencies[ $tradeable ][ 'payin' ] ) { + logg( $this->prefix() . "Coin '$tradeable' is disabled by Exchange!", true ); + return false; + } + if ( !$this->exchange->currencies[ $tradeable ][ 'payout' ] || !$this->exchange->currencies[ $tradeable ][ 'payin' ] ) { + logg( $this->prefix() . "Coin '$tradeable' is disabled by Exchange!", true ); + return false; + } + + return $market[ 'active' ]; + } + + public function checkAPIReturnValue( $result ) { + if ( isset( $result[ 'error' ] ) ) { + return false; + } + if ( isset( $result[ 'address' ] ) ) { + return true; + } + if ( isset( $result[ 'id' ] ) ) { + return true; + } + return $result[ 'result' ] === true; + } + + public function getDepositHistory() { + + $history = []; + $results = $this->exchange->privateGetAccountTransactions(); + + if(is_array($results)){ + foreach ($results as $row){ + if(!in_array($row[ 'type' ], [ 'payin', 'deposit'])){ + continue; + } + $history[] = $row; + } + } + + return array( + + 'history' => $history, + 'statusKey' => 'status', + 'coinKey' => 'currency', + 'amountKey' => 'amount', + 'timeKey' => 'createdAt', + 'txidKey' => 'id', + 'addressKey' => 'address', + 'pending' => 'pending', + 'completed' => 'success', + + ); + + } + + public function getWithdrawalHistory() { + + $history = []; + $results = $this->exchange->privateGetAccountTransactions(); + + if(is_array($results)){ + foreach ($results as $row){ + if(!in_array($row[ 'type' ], [ 'payout', 'withdraw'])){ + continue; + } + $history[] = $row; + } + } + + return array( + + 'history' => $history, + 'statusKey' => 'status', + 'coinKey' => 'currency', + 'amountKey' => 'amount', + 'timeKey' => 'createdAt', + 'txidKey' => 'id', + 'addressKey' => 'address', + 'pending' => 'pending', + 'completed' => 'success', + + ); + + } + + // Changed functions! + + public function getTickers( $currency ) { + + $currency = $this->coinNames[ $currency ]; + $markets = $this->exchange->loadMarkets(); + + $tickers = $this->exchange->fetchTickers( [] ); + $ticker = [ ]; + foreach ( $markets as $market ) { + if ( $market[ 'quote' ] != $currency ) { + continue; + } + // The API doesn't provide the last price, so we just take an average into our spread. :-( + $ticker[ $market[ 'base' ] ] = formatBTC( $tickers[ $market[ 'symbol' ] ][ 'info' ][ 'last' ] ); + } + + return $ticker; + + } + + public function getDepositAddress( $coin ) { + + $coin = $this->coinNames[ $coin ]; + if ( !$this->exchange->currencies[ $coin ][ 'payin' ] ) { + logg( $this->prefix() . "Coin '$coin' is disabled now for deposit!", true ); + return null; + } + $result = $this->exchange->fetch_deposit_address( $coin ); + if ( !isset( $result[ 'address' ] ) ) { + logg( $this->prefix() . "Please generate a deposit address for $coin!", true ); + return null; + } + if ( !is_null( $result[ 'tag' ] ) ) { + return array( $result[ 'address' ], $result[ 'tag' ] ); + } + return $result[ 'address' ]; + + } + + public function withdraw( $coin, $amount, $address, $tag = null ) { + + $coin = $this->coinNames[ $coin ]; + if ( !$this->exchange->currencies[ $coin ][ 'payout' ] ) { + logg( $this->prefix() . "Coin '$coin' is disabled now for withdraw!", true ); + return false; + } + if ( floatval( $amount ) === 0 ) { + // Simulate an error which ccxt may not raise for us + throw new Exception( "API error response: Amount must be greater than zero" ); + } + + $precisions = $this->getPrecision( $coin, 'BTC' ); + $amount = sprintf( "%." . $precisions[ 'amount' ] . "f", $amount ); + + $result = $this->exchange->privatePostAccountTransfer(array ( + 'currency' => $coin, + 'amount' => floatval ($amount), + 'type' => 'exchangeToBank', + )); + sleep(1); + + return $this->checkAPIReturnValue( $this->exchange->withdraw( $coin, $amount, $address, null, [ 'includeFee' => 'true' ] ) ); + + } + + private function common_currency_code ($currency) { +// if ($currency == 'BTC') +// return 'XBT'; +// if ($currency == 'DASH') +// return 'DRK'; + if ($currency == 'BITCLAVE') + return 'CAT'; +// if ($currency == 'USDT') +// return 'USD'; + return $currency; + } + + public function buy( $tradeable, $currency, $rate, $amount ) { + try { + $precisions = $this->getPrecision( $tradeable, $currency ); + $limits = $this->getLimits( $tradeable, $currency ); + $fee = $this->tradeFees[ $tradeable . "_" . $currency ]; + if($limits[ 'amount' ][ 'min' ] > $amount){ + logg( $this->prefix() . "Amount for $tradeable less MIN in buy()!" ); + return null; + } +// $amount_fee = round($amount * $fee, $precisions[ 'amount' ]); + $amount_fee = $amount * $fee; + print( $this->prefix() . "INFO BUY [$tradeable]:\n" ); + print( $this->prefix() . "Limits = " . $limits[ 'amount' ][ 'min' ] . " Precisions = " . $precisions[ 'amount' ] . "\n" ); + print( $this->prefix() . "Amount = " . $amount . "\n" ); + print( $this->prefix() . "Fee = " . $fee . " => " . $amount_fee . "\n" ); +// $amount = round($amount + $amount_fee, $precisions[ 'amount' ]); + $amount = $amount + $amount_fee; + print( $this->prefix() . "Total = " . $amount . "\n" ); + + $result = $this->exchange->create_order( $this->common_currency_code ($tradeable) . '/' . $this->common_currency_code ($currency), 'limit', 'buy', + $amount, $rate ); + return $currency . '_' . $tradeable . ':' . $result[ 'id' ]; + } + catch ( Exception $ex ) { + logg( $this->prefix() . "Got an exception in buy(): " . $ex->getMessage() ); + return null; + } + } + + public function sell( $tradeable, $currency, $rate, $amount ) { + try { + $precisions = $this->getPrecision( $tradeable, $currency ); + $limits = $this->getLimits( $tradeable, $currency ); + $fee = $this->tradeFees[ $tradeable . "_" . $currency ]; + if($limits[ 'amount' ][ 'min' ] > $amount){ + logg( $this->prefix() . "Amount for $tradeable less MIN in sell() [" . $limits[ 'amount' ][ 'min' ] . "]!" ); + return null; + } +// $amount_fee = round($amount * $fee, $precisions[ 'amount' ]); + $amount_fee = $amount * $fee; + print( $this->prefix() . "INFO SELL [$tradeable]:\n" ); + print( $this->prefix() . "Limits = " . $limits[ 'amount' ][ 'min' ] . " Precisions = " . $precisions[ 'amount' ] . "\n" ); + print( $this->prefix() . "Amount = " . $amount . "\n" ); + print( $this->prefix() . "Fee = " . $fee . " => " . $amount_fee . "\n" ); +// $amount = round($amount - $amount_fee, $precisions[ 'amount' ]); + $amount = $amount - $amount_fee; + print( $this->prefix() . "Total = " . $amount . "\n" ); + + $result = $this->exchange->create_order( $this->common_currency_code ($tradeable) . '/' . $this->common_currency_code ($currency), 'limit', 'sell', + $amount, $rate ); + return $currency . '_' . $tradeable . ':' . $result[ 'id' ]; + } + catch ( Exception $ex ) { + logg( $this->prefix() . "Got an exception in sell(): " . $ex->getMessage() ); + return null; + } + } + + public function cancelAllOrders() { + + $pairs = $this->getWithdrawablePairs(); + foreach ( $pairs as $pair ) { + $split = explode( '_', $pair ); + $currency = $split[ 1 ]; + $tradeable = $split[ 0 ]; + $currency = $this->coinNames[ $currency ]; + $tradeable = $this->coinNames[ $tradeable ]; + + $orders = $this->exchange->fetch_open_orders( $tradeable . '/' . $currency ); + foreach ( $orders as $order ) { + $orderID = $order[ 'order' ]; + $split = explode( '/', $order[ 'symbol' ] ); + $tradeable = $split[ 0 ]; + $currency = $split[ 1 ]; + $tradeable = $this->coinNames[ $tradeable ]; + $currency = $this->coinNames[ $currency ]; + $this->cancelOrder( $currency . '_' . $tradeable . ':' . $orderID ); + } + } + + } + + private function exchangeBalanceToTrade() { + $balances = $this->exchange->privateGetAccountBalance(); + if(is_array($balances)){ + foreach ($balances as $balance){ + $coin = $balance[ 'currency' ]; + $amount = $balance[ 'available' ]; + if($amount == 0){ + continue; + } + $this->exchange->privatePostAccountTransfer( array( + 'currency' => $coin, + 'amount' => $amount, + 'type' => 'bankToExchange' + ) ); + } + } + } + + private function queryBalances() { + return $this->exchange->fetch_balance()[ 'free' ]; + } + + public function refreshWallets( $tradesMade = array() ) { + + $this->exchangeBalanceToTrade(); + + $this->preRefreshWallets(); + + $wallets = [ ]; + + $arr = $this->queryBalances(); + foreach ( $arr as $coin => $balance ) { + $wallets[ strtoupper( $coin ) ] = $balance; + } + + $this->wallets = $wallets; + + $this->postRefreshWallets( $tradesMade ); + + } + +// public function refreshWallets( $inBetweenTrades = false ) { +// +// $this->exchangeBalanceToTrade(); +// +//// if ( !$inBetweenTrades ) { +//// $this->preRefreshWallets(); +//// } +// +// $wallets = [ ]; +// +// $currencies = $this->exchange->currencies; +// foreach ($currencies as $coin => $row){ +// $wallets[ strtoupper( $coin ) ] = 0; +// } +// +// $arr = $this->queryBalances(); +// foreach ( $arr as $coin => $balance ) { +// $wallets[ strtoupper( $coin ) ] = $balance; +// } +// +// $this->wallets = $wallets; +// +//// if ( !$inBetweenTrades ) { +//// $this->postRefreshWallets(); +//// } +// +// } + + protected function fetchOrderbook( $tradeable, $currency ) { + + $tradeableInternal = $this->coinNames[ $tradeable ]; + $currencyInternal = $this->coinNames[ $currency ]; + $orderbook = $this->exchange->fetch_order_book( $tradeableInternal . '/' . $currencyInternal ); + if ( count( $orderbook ) == 0 ) { + return null; + } + + if ( !key_exists( 'asks', $orderbook ) ) { + return null; + } + $asks = $orderbook[ 'asks' ]; + if ( count( $asks ) == 0 ) { + return null; + } + $bestAsk = $asks[ 0 ]; + + if ( !key_exists( 'bids', $orderbook ) ) { + return null; + } + $bids = $orderbook[ "bids" ]; + if ( count( $bids ) == 0 ) { + return null; + } + $bestBid = $bids[ 0 ]; + + return new Orderbook( // + $this, $tradeable, // + $currency, // + new OrderbookEntry( $bestAsk[ 1 ], $bestAsk[ 0 ] ), // + new OrderbookEntry( $bestBid[ 1 ], $bestBid[ 0 ] ) // + ); + + } + + public function refreshExchangeData() { + + $pairs = [ ]; + $precisions = [ ]; + $limits = [ ]; + $tradeFees = [ ]; + $conf = [ ]; + $markets = $this->exchange->loadMarkets(); + + foreach ( $markets as $market ) { + + $tradeable = $market[ 'base' ]; + $currency = $market[ 'quote' ]; + + if ( !Config::isCurrency( $currency ) || + Config::isBlocked( $tradeable ) ) { + continue; + } + + if ( !$this->isMarketActive( $market ) ) { + continue; + } + + $this->coinNames[ strtoupper( $tradeable ) ] = $tradeable; + $this->coinNames[ strtoupper( $currency ) ] = $currency; + + $pair = strtoupper( $tradeable . "_" . $currency ); + $pairs[] = $pair; + $precisions[ $pair ] = $market[ 'precision' ]; + $limits[ $pair ] = $market[ 'limits' ]; + // We can't be sure whether we'll be the maker or the taker in each trade, so we'll assume the worst. + $tradeFees[ $pair ] = max( $market[ 'maker' ], $market[ 'taker' ] ); + $conf[ $tradeable ] = 0; // unknown! + } + + $this->pairs = $pairs; + $this->confirmationTimes = $conf; + $this->tradeFees = $tradeFees; + $this->precisions = $precisions; + $this->limits = $limits; + $this->withdrawFees = $this->exchange->fees[ 'funding' ][ 'withdraw' ]; + $this->depositFees = $this->exchange->fees[ 'funding' ][ 'deposit' ]; + + $this->calculateTradeablePairs(); + + } + + public function testAccess_() { + $this->refreshExchangeData(); +// $markets = $this->exchange->loadMarkets(); + + var_dump($this->coinNames['KMD']);die(); +// var_dump($this->isMarketActive($markets['KMD/BTC']));die(); + } + +}; diff --git a/bot/xchange/map.11 b/bot/xchange/map.11 new file mode 100644 index 0000000..797ef74 --- /dev/null +++ b/bot/xchange/map.11 @@ -0,0 +1 @@ +Bxintx \ No newline at end of file diff --git a/bot/xchange/map.13 b/bot/xchange/map.13 new file mode 100644 index 0000000..cd86046 --- /dev/null +++ b/bot/xchange/map.13 @@ -0,0 +1 @@ +Kraken \ No newline at end of file diff --git a/bot/xchange/map.5 b/bot/xchange/map.5 new file mode 100644 index 0000000..f30be2b --- /dev/null +++ b/bot/xchange/map.5 @@ -0,0 +1 @@ +Bitfinex \ No newline at end of file diff --git a/bot/xchange/map.6 b/bot/xchange/map.6 new file mode 100644 index 0000000..65132e7 --- /dev/null +++ b/bot/xchange/map.6 @@ -0,0 +1 @@ +Cryptopia \ No newline at end of file diff --git a/bot/xchange/map.7 b/bot/xchange/map.7 new file mode 100644 index 0000000..1ae6928 --- /dev/null +++ b/bot/xchange/map.7 @@ -0,0 +1 @@ +Hitbtc \ No newline at end of file diff --git a/web/ui.js b/web/ui.js index 42ba883..acef3ad 100644 --- a/web/ui.js +++ b/web/ui.js @@ -91,7 +91,7 @@ $(function() { function no2e(x, title) { var num = parseInt(x); - if (num <= 9 && num >= 0) { + if (num <= 100 && num >= 0) { return '' + '' + ''; @@ -111,10 +111,18 @@ $(function() { return "BLTRD"; else if (x === "3" || x === 3) return "BTTRX"; + else if (x === "5" || x === 5) + return "BTFNX"; + else if (x === "6" || x === 6) + return "CRPTP"; else if (x === "7" || x === 7) return "HTBTC"; else if (x === "9" || x === 9) return "BINCE"; + else if (x === "11" || x === 11) + return "BXTH"; + else if (x === "13" || x === 13) + return "KRKN"; else return "?"; @@ -137,10 +145,18 @@ $(function() { return "Bleutrade"; else if (x === "3" || x === 3) return "Bittrex"; + else if (x === "5" || x === 5) + return "Bitfinex"; + else if (x === "6" || x === 6) + return "Cryptopia"; else if (x === "7" || x === 7) return "HitBTC"; else if (x === "9" || x === 9) return "Binance"; + else if (x === "11" || x === 11) + return "Bxinth"; + else if (x === "13" || x === 13) + return "Kraken"; else return "?"; diff --git a/web/xchange/11.ico b/web/xchange/11.ico new file mode 100644 index 0000000..d4b2fbf Binary files /dev/null and b/web/xchange/11.ico differ diff --git a/web/xchange/13.ico b/web/xchange/13.ico new file mode 100755 index 0000000..a926177 Binary files /dev/null and b/web/xchange/13.ico differ diff --git a/web/xchange/5.ico b/web/xchange/5.ico new file mode 100644 index 0000000..65041a2 Binary files /dev/null and b/web/xchange/5.ico differ diff --git a/web/xchange/6.ico b/web/xchange/6.ico new file mode 100755 index 0000000..2160006 Binary files /dev/null and b/web/xchange/6.ico differ