def test_basic_crypto_crypto_minimally_profitable(self): """ OB1 has a bid at 600 and OB2 has an ask at 599, both at 1btc. These should cross for a volume of 2btc and a profit of $1 USD. """ self.itbit.market_order_fee = Decimal('0.0008') self.bitstamp.market_order_fee = Decimal('0.0008') result = arbitrage.detect_cross( self.basic_ob_2(price_currency='BTC', vol_currency='ETH'), self.basic_ob_1(price_currency='BTC', vol_currency='ETH'), ) result.volume.should.equal(Money('1', 'ETH')) result.revenue.should.equal(Money('1', 'BTC')) result.fees.should.equal(Money('0.9592', 'BTC')) result.profit.should.equal(Money('0.0408', 'BTC'))
def __init__(self, session=None, configuration=None): # 2018-4-4: This is a hack for an outstanding issue in our environment that # causes bitstamp to reject all but the first requests made with a # requests.Session() object. See trello for more information. if not session: session = FuturesSession(max_workers=10) session.cookies = ForgetfulCookieJar() super(BitstampBTCUSDExchange, self).__init__(session) # Immutable properties. # TODO: Check on status of the withdrawal_requests_url (might need a dash). # TODO: Check if the withdraw_url is still being used or why it isn't in the # v2 API. self.name = u'BITSTAMP_BTC_USD' self.friendly_name = u'Bitstamp BTC-USD' self.base_url = 'https://www.bitstamp.net/api/v2/' self.currency = u'USD' self.volume_currency = 'BTC' self.price_decimal_precision = 2 self.volume_decimal_precision = 8 # Configurables defaults. self.market_order_fee = self.fee self.limit_order_fee = self.fee self.fee = Decimal('0.0005') # TODO: update these. self.fiat_balance_tolerance = Money('0.0001', 'USD') self.volume_balance_tolerance = Money('0.00000001', 'BTC') self.max_tick_speed = 1 self.min_order_size = Money('0.001', 'BTC') self.use_cached_orderbook = False if configuration: self.configure(configuration) # Endpoints. self.ticker_url = 'ticker/btcusd/' self.orderbook_url = 'order_book/btcusd/' self.buy_url = 'buy/btcusd/' self.sell_url = 'sell/btcusd/' self.open_orders_url = 'open_orders/btcusd/' self.trade_status_url = 'user_transactions/btcusd/' self.balance_url = 'balance/' self.trade_cancel_url = 'cancel_order/' self.withdrawl_requests_url = 'withdrawal_requests/' self.withdraw_url = 'https://priv-api.bitstamp.net/api/bitcoin_withdrawal/'
def parse_trades(self, resp_obj): trades = [] for trade in resp_obj: # No unique id comes back with the trade so we use the milliseconds # timestamp. trade = { 'exchange': self.exchange_name, 'price': Money(trade['rate'], 'CAD'), 'volume': Money(trade['amount'], 'BTC'), 'timestamp': int(trade['datetime']) / 1000.0, 'trade_id': int(trade['datetime']), } trades.append(trade) return trades
def get_open_orders_resp(self, req): response = self.resp(req) return [{ 'mode': self._order_mode_to_const(order['side']), 'id': str(order['orderId']), 'price': Money(order['price'], self.currency), 'type': self.binance_type_to_order_type[order['type']], 'volume': Money(order['origQty'], self.volume_currency), 'volume_remaining': Money(order['origQty'], self.volume_currency) - Money(order['executedQty'], self.volume_currency), } for order in response]
def test_real_data_unprofitable_flag_off(self): """ Test based on real data taken from itbit and bitstamp at about 4:30 on September 26 2016 against math done by hand. """ self.itbit.market_order_fee = Decimal('0.10') self.bitstamp.market_order_fee = Decimal('0.10') result = arbitrage.detect_cross( self.real_orderbook_1(), self.real_orderbook_2(), ignore_unprofitable=False, ) result.volume.should.equal(Money('12.9087178', 'BTC')) result.revenue.should.equal(Money('5.2679425048', 'USD')) assert result.profit < Money('0', 'USD')
def get_balance_resp(self, req): response, headers = self.resp(req) balance = Balance() try: for account in response: if account['currency'] == 'BTC': balance['BTC'] = Money(account['available'], 'BTC') elif account['currency'] == self.currency: balance[self.currency] = Money(account['available'], self.currency) except KeyError: raise exceptions.ExchangeAPIErrorException(self, 'malformed response') return balance
def check_btc_net_assets(db): btc_net_assets_error = assets.calculate_btc_net_assets_error(db) if btc_net_assets_error == Money(0, 'BTC'): succeed('BTC_NET_ASSETS') else: detail = 'Error: %s' % btc_net_assets_error fail('BTC_NET_ASSETS', detail)
def setUp(self): self.order = mock.MagicMock() self.order.exchange_rate = Decimal('0.80') self.order.fundamental_value = Money('250', 'USD') self.trades = [] self.bid = Trade( Consts.BID, Money('100', 'USD'), Money('0', 'USD'), Money('1', 'BTC'), '1', self.order, ) self.trades.append(self.bid) self.ask = Trade( Consts.ASK, Money('100', 'USD'), Money('0', 'USD'), Money('1', 'BTC'), '2', self.order, ) self.trades.append(self.ask)
def calculate_outstanding_btc_fees(db): raw_trade_btc_fees = db.query(func.sum(Trade._fee))\ .filter(Trade.has_outstanding_btc_fee)\ .scalar() raw_transaction_btc_fees = db.query(func.sum(Transaction._fee))\ .filter(Transaction.has_outstanding_btc_fee)\ .scalar() if raw_trade_btc_fees == None: raw_trade_btc_fees = 0 if raw_transaction_btc_fees == None: raw_transaction_btc_fees = 0 trade_btc_fees = Money(raw_trade_btc_fees, 'BTC') transaction_btc_fees = Money(raw_transaction_btc_fees, 'BTC') return trade_btc_fees + transaction_btc_fees
def _get_midpoint(self, orderbook): bid_quote = Exchange.price_quote_from_orderbook( orderbook, 'BID', Money(20, 'BTC'), ) bid_price = float(bid_quote['price_for_order']) ask_quote = Exchange.price_quote_from_orderbook( orderbook, 'ASK', Money(20, 'BTC'), ) ask_price = float(ask_quote['price_for_order']) return (bid_price + ask_price) / 2
def get_open_orders_resp(self, req): raw_open_orders = self.resp(req) raw_open_orders = raw_open_orders if raw_open_orders else [] open_orders = [] for raw_order in raw_open_orders: mode = self._order_mode_to_const(str(raw_order['type'])) order = { 'mode': mode, 'id': str(raw_order['id']), 'price': Money(raw_order['price'], 'CAD'), 'volume_remaining': Money(raw_order['amount'], 'BTC'), } open_orders.append(order) return open_orders
def period(self, step='1d', sid=''): from twisted.internet import defer points = yield self.req(self.create_url(step)) if not points: defer.returnValue(OrderedDict()) point_hash = OrderedDict() for p in points: point_hash[epoch(p[0]).naive] = Money(str(p[7]), 'BTC') defer.returnValue(point_hash)
def get_all_fees_in_period_by_exchange_in_usd(db, start, end): fees_by_exchange = db\ .query( func.sum(Trade.fee_in_usd), Order._exchange_name, )\ .join(Order)\ .filter(Trade.time_created >= start)\ .filter(Trade.time_created < end)\ .group_by(Order._exchange_name)\ .all() exchange_fees = defaultdict(lambda: Money(0, 'USD')) for fees, exchange_name in fees_by_exchange: exchange_fees[exchange_name] = Money(fees, 'USD') return exchange_fees
def test_trivial_ask(self): mode = Consts.ASK initial_price = Money('1002', 'USD') orderbook = self.basic_a ignore_volume = Money('0.001', 'BTC') jump = Money('0.01', 'USD') max_slide = None new_price = order_sliding.slide_order( mode, initial_price, orderbook, ignore_volume, jump, max_slide, ) new_price.should.equal(Money('1500.99', 'USD'))
def test_do_not_ignore_bid(self): mode = Consts.BID initial_price = Money('999', 'USD') orderbook = self.basic_b ignore_volume = Money('0.001', 'BTC') jump = Money('0.01', 'USD') max_slide = None new_price = order_sliding.slide_order( mode, initial_price, orderbook, ignore_volume, jump, max_slide, ) new_price.should.equal(Money('900.01', 'USD'))
def generate_null_data(self): open_orders_for_exchange = {} orderbooks = defaultdict(lambda x: []) levels_for_exchange = {e: [] for e in self.trading_pair_names} args = { 'gds_active': False, 'levels_for_exchange': levels_for_exchange, 'selected_exchanges': [''], 'open_orders_for_exchange': open_orders_for_exchange, 'high': Money('1', 'USD'), 'low': Money('0', 'USD'), 'pair_name': self.pair_name, 'show_orders': False, 'at_time': None, } return args
def test_simple(self): configuration = { 'platform': { 'emerald': True }, 'strategy': {}, 'exchanges': { self.exchange_name: { 'fiat_balance_tolerance': Money('1', self.price_currency), }, } } exchange = self.exchange_class(configuration=configuration) assert exchange.fiat_balance_tolerance == Money( '1', self.price_currency) assert exchange.use_cached_orderbook == True
def test_basic_crypto_crypto_unprofitable_with_ignore_flag_off(self): """ OB1 has a bid at 600 and OB2 has an ask at 599, both at 1btc. These should cross for a volume of 2btc and a profit of $1 USD. """ self.itbit.market_order_fee = Decimal('0.1') self.bitstamp.market_order_fee = Decimal('0.1') result = arbitrage.detect_cross( self.basic_ob_2(price_currency='BTC', vol_currency='ETH'), self.basic_ob_1(price_currency='BTC', vol_currency='ETH'), ignore_unprofitable=False, ) result.volume.should.equal(Money('1', 'ETH')) result.revenue.should.equal(Money('1', 'BTC')) result.fees.should.equal(Money('119.9', 'BTC'))
def more_ob_1(self, price_currency='USD', vol_currency='BTC'): ex = self.bitstamp volume = Money('1', vol_currency) ex.currency = price_currency ex.volume_currency = vol_currency bids3 = [ Order(Money('600', price_currency), volume, ex, Consts.BID), Order(Money('550', price_currency), volume, ex, Consts.BID), ] asks3 = [ Order(Money('601', price_currency), volume, ex, Consts.ASK), Order(Money('650', price_currency), volume, ex, Consts.ASK), ] return {'bids': bids3, 'asks': asks3}
def order_details_resp(self, reqs): details_response = self.resp(reqs['details']) fee = self.order_fee_resp(reqs['fee']) if details_response['orders']: raw_order = details_response['orders'][0] order = self.parse_raw_order(raw_order, fee) return order else: empty_order = { 'time_created': None, 'type': None, 'btc_total': Money(0, 'BTC'), 'fiat_total': Money(0, 'USD'), 'trades': [], } return empty_order
def balance_resp(self, req): response = self.resp(req) try: balances = response['info']['funds'] btc_available = Money(balances['free']['btc'], 'BTC') usd_available = Money(balances['free']['usd'], 'USD') except KeyError: raise exceptions.ExchangeAPIErrorException( self, 'Balance missing expected keys', ) balance = Balance() balance['BTC'] = btc_available balance['USD'] = usd_available return balance
def test_standardization_exchanges(self): base_config = { 'platform': { 'audit': False }, 'strategy': { 'tick_sleep': Decimal('1') }, 'coinbase_btc_usd': { 'fiat_balance_tolerance': Money('0.01', 'USD') } } new_config = config_helper.format_file_config_to_standard(base_config) len(new_config['exchanges']).should.equal(1) len(new_config['exchanges']['coinbase_btc_usd'].keys()).should.equal(1) new_config['exchanges']['coinbase_btc_usd']['fiat_balance_tolerance']\ .should.equal(Money('0.01', 'USD'))
def all_fees(trades, price_currency=None, volume_currency='BTC'): """ Return the fiat equivalent fees for a list of trades and the exact BTC amount of any BTC fees (their USD value is still included in the total). """ if not price_currency: price_currency = price_currency_for_trades(trades) all_fees = Money('0', price_currency) all_volume_currency_fees = Money('0', volume_currency) for t in trades: if t.fee.currency == volume_currency: all_volume_currency_fees += t.fee all_fees += t.fee_in_currency(price_currency) return all_fees, all_volume_currency_fees
def test_real_data_directional(self): """ Test that the directional calculations work as expected against real data. """ result1 = arbitrage.detect_directional_cross( self.real_orderbook_1(), self.real_orderbook_2(), ) result1.volume.should.equal(Money('12.9087178', 'BTC')) result1.revenue.should.equal(Money('5.2679425048', 'USD')) result2 = arbitrage.detect_directional_cross( self.real_orderbook_2(), self.real_orderbook_1(), ) result2.should.equal(None)
def get_order_details_resp(self, req): response = self.resp(req) volume_amount = Money(response['origQty'], self.volume_currency) executed_volume_amount = Money(response['executedQty'], self.volume_currency) price = Money(response['price'], self.currency) # Cannot multiple Money * Money, we need to know the unit, so we use the currency unit and a Decimal price_currency_amount = price * Decimal(response['executedQty']) result = { 'id': str(response['orderId']), 'time_created': int(response['time']), 'mode': self._order_mode_to_const(response['side']), 'type': self.binance_type_to_order_type[response['type']], self.volume_currency.lower() + '_total': volume_amount, self.currency.lower() + '_total': price_currency_amount, # 'trades': [], # Binance does not split orders into trades, just partial fill quantities } return result
def basic_ob_1(self, price_currency='USD', vol_currency='BTC'): ex = self.itbit ex.currency = price_currency ex.volume_currency = vol_currency volume = Money('1', vol_currency) bids2 = [ Order(Money('598', price_currency), volume, ex, Consts.BID), Order(Money('550', price_currency), volume, ex, Consts.BID), ] asks2 = [ Order(Money('599', price_currency), volume, ex, Consts.ASK), Order(Money('650', price_currency), volume, ex, Consts.ASK), ] return {'bids': bids2, 'asks': asks2}
def test_creation_with_details(self): l = Liability( Money('100', 'ETH'), Liability.FIXED_INTEREST, 'John', time_started=parse('2016-10-1').datetime, details={'interest_rate': 0.05}, ) l.details['interest_rate'].should.equal(0.05)
def fiat_withdrawal_fee(self, withdrawal_amount): """ Itbit fee is from their documentation, and an extra $15 is being charged to us before it shows up in our bank account (as of the September 2016), so I assume that's an intermediary fee. The fee should be a flat $50 on withdrawals > $10k, but we'll see. """ fee = Money('0', 'USD') if withdrawal_amount < Money('10,000', 'USD'): itbit_fee = Money('15', 'USD') intermediary_fee = Money('15', 'USD') fee = itbit_fee + intermediary_fee else: fee = Money('50', 'USD') return fee
def __init__(self, session=None, currency=u"CAD", use_cached_orderbook=False): super(VaultOfSatoshiExchange, self).__init__(session) self.name = u'VAULTOFSATOSHI' self.friendly_name = u'Vault of Satoshi' self.base_url = 'https://api.vaultofsatoshi.com' self.currency = currency self.fee = Decimal("0") # assuming $99 unlimited account self.withdrawal_fee = Money("0.0005", "BTC") self.bid_string = "bid" self.ask_string = "ask" self.use_cached_orderbook = use_cached_orderbook
def order_fee_resp(self, req): try: response = self.resp(req) except CancelOrderNotFoundError: # OkCoin has started returning this error on non-executed orders or orders # with no fees. return Money('0', 'USD') # Okcoin returns fees as negative values. fee_amount = abs(Decimal(response['data']['fee'])) fee_currency = 'USD' # If the order has not been filled, response['data']['type] is not # in the response. if 'type' in response['data']: fee_currency = response['data']['type'].upper() fee = Money(fee_amount, fee_currency) return fee