class Bcex(Market): ORDER_BOOK_DEPTH = 10 def __init__(self, currency, code): super().__init__(currency) self.code = code self.update_rate = 30 self._client = BcexInterface(symbols=[code],env=Environment.PROD, channels=[Channel.L2]) self._client.connect() def update_depth(self): ob = self._client.get_order_book(self.code) self.depth = self.format_depth(ob) #KEY IS PRICE VALUE IS AMOUNT SORTED LOW TO HIGH def sort_and_format(self, l, reverse=False): r = [] for i in range(self.ORDER_BOOK_DEPTH): if not reverse: if len(l) >= i: item = l.peekitem(i) r.append({"price": float(item[0]), "amount": float(item[1])}) else: if len(l) >= i: item = l.peekitem(-(i+1)) r.append({"price": float(item[0]), "amount": float(item[1])}) return r def format_depth(self, depth): bids = self.sort_and_format(depth["bids"], True) asks = self.sort_and_format(depth["asks"], False) return {"asks": asks, "bids": bids}
def test_get_last_traded_price(self, mock_client): exi = BcexInterface(symbols=[Symbol.ETHBTC]) assert mock_client.call_count == 1 act = exi.get_last_traded_price(Symbol.ETHBTC) assert act is None exi.client.tickers.update( {Symbol.ETHBTC: { "volume_24h": 13.1, "price_24h": 21.139 }}) act = exi.get_last_traded_price(Symbol.ETHBTC) assert act is None exi.client.tickers = { Symbol.ETHBTC: { "volume_24h": 13.1, "price_24h": 21.139, "last_trade_price": 20.120, } } act = exi.get_last_traded_price(Symbol.ETHBTC) assert act == 20.120
def test_exit(self, mock_client): exi = BcexInterface(symbols=[Symbol.BTCUSD]) assert mock_client.call_count == 1 exi.client.exit = Mock() exi.exit() assert exi.client.exit.call_count == 1
def test_connect(self, mock_client): exi = BcexInterface(symbols=[Symbol.ETHBTC]) assert mock_client.call_count == 1 exi.client.connect = Mock() exi.connect() assert exi.client.connect.call_count == 1
def test_scale_price(self): ins_details = { "min_price_increment": 10, "min_price_increment_scale": 2 } assert BcexInterface._scale_price(ins_details, 1000) == 1000 assert BcexInterface._scale_price(ins_details, 1000.001) == 1000 assert BcexInterface._scale_price(ins_details, 1000.01) == 1000 assert BcexInterface._scale_price(ins_details, 1000.1) == 1000.1
class BaseTrader: CHANNELS = Channel.PRIVATE + [Channel.TICKER, Channel.SYMBOLS] def __init__( self, symbol, api_key=None, env=Environment.STAGING, refresh_rate=5, channels_kwargs=None, ): self.exchange = BcexInterface( [symbol], api_secret=api_key, env=env, channels=self.CHANNELS, channel_kwargs=channels_kwargs, ) self.exchange.connect() self._symbol = symbol self._refresh_rate = refresh_rate # TODO: might want to extend the cancellation of open orders to symbol specific? self.reset() @property def refresh_rate(self): return self._refresh_rate @property def symbol(self): return self._symbol def reset(self): logging.info("Cancelling all existing orders before moving forward") self.exchange.cancel_all_orders() def handle_orders(self): raise NotImplementedError("This should be implemented in subclass") def restart(self): # This is what bitmex one does - seems like it executes to whole script again - pretty cool? logging.warning("Restarting the market maker...") os.execv(sys.executable, [sys.executable] + sys.argv) def run_loop(self): while True: if not self.exchange.is_open(): logging.error( "Websocket has disconnected. Attempting to restart") self.restart() self.handle_orders() time.sleep(self.refresh_rate)
def test_is_open(self, mock_client): exi = BcexInterface(symbols=[Symbol.ETHBTC]) assert mock_client.call_count == 1 assert not exi.is_open() exi.client.ws = Mock() assert exi.is_open() exi.client.exited = True assert not exi.is_open()
def test_check_quantity_within_limits(self): ins_details = { "min_order_size": 50, "min_order_size_scale": 2, "max_order_size": 500, "max_order_size_scale": 2, } assert not BcexInterface._check_quantity_within_limits( ins_details, 0.01) assert BcexInterface._check_quantity_within_limits(ins_details, 0.6) assert BcexInterface._check_quantity_within_limits(ins_details, 1) assert not BcexInterface._check_quantity_within_limits( ins_details, 5.1)
def test_tick_size(self, mock_client): exi = BcexInterface(symbols=[Symbol.BTCPAX]) assert mock_client.call_count == 1 # no information about symbol assert exi.tick_size(Symbol.BTCPAX) is None exi.client.symbol_details[Symbol.BTCPAX] = { "min_price_increment": 6, "min_price_increment_scale": 3, } act_tick_size = exi.tick_size(Symbol.BTCPAX) assert act_tick_size == 0.006
def main(): ex_interface = BcexInterface( symbols=[Symbol.ETHBTC, Symbol.BTCUSD], channels=[ Channel.HEARTBEAT, Channel.L2, Channel.PRICES, Channel.SYMBOLS, Channel.TICKER, Channel.TRADES, Channel.AUTH, Channel.BALANCES, Channel.TRADING, ], env=Environment.STAGING, ) attempt_number = 1 while attempt_number <= RETRY_NUMBER: try: sleep_time = 5 levels = 5 quote_randomly_both_sides_interface(ex_interface, levels, sleep_time) except WebSocketConnectionClosedException as e: logging.error(f"Attempt Number {attempt_number} errored with {e}") attempt_number += 1 except Exception as e: raise e
def test_lot_size(self, mock_client): exi = BcexInterface(symbols=[Symbol.ETHBTC]) assert mock_client.call_count == 1 # no information about symbol assert exi.lot_size(Symbol.ETHBTC) is None exi.client.symbol_details[Symbol.ETHBTC] = { "min_order_size": 50, "min_order_size_scale": 2, } act_lot_size = exi.lot_size(Symbol.ETHBTC) assert act_lot_size == 0.5 # another symbol assert exi.lot_size(Symbol.ALGOBTC) is None
class PrivateBcexUSD(Market): def __init__(self): super().__init__() self.api_secret = config.bcex_api_secret self.get_info() self._client = BcexInterface(symbols=["BTC-USD"], env=Environment.PROD) self._client.connect() def buy(self, amount, price): """Create a buy limit order""" self._client.place_order("BTC-USD", OrderSide.BUY, price=price, quantity=amount) def _sell(self, amount, price): """Create a sell limit order""" self._client.place_order("BTC-USD", OrderSide.SELL, price=price, quantity=amount) def get_info(self): """Get balance""" balances = self._client.get_balances() self.btc_balance = float(balances["BTC"]["available"]) self.usd_balance = float(balances["USD"]["available"])
def test_init(self, mock_client): mock_client.side_effect = MockBcexClient exi = BcexInterface( [Symbol.ETHBTC, Symbol.BTCPAX], api_secret="14567", env=Environment.STAGING, channels=None, channel_kwargs={Channel.PRICES: { "granularity": 60 }}, cancel_position_on_exit=False, ) assert mock_client.call_count == 1 assert mock_client.call_args[0][0] == [Symbol.ETHBTC, Symbol.BTCPAX] assert mock_client.call_args[1]["api_secret"] == "14567" assert mock_client.call_args[1]["env"] == Environment.STAGING assert mock_client.call_args[1]["channels"] is None assert mock_client.call_args[1]["channel_kwargs"] == { Channel.PRICES: { "granularity": 60 } } assert mock_client.call_args[1]["cancel_position_on_exit"] is False mock_client.reset_mock() exi = BcexInterface( [Symbol.ETHBTC, Symbol.BTCPAX], api_secret=None, env=Environment.PROD, channels=[Channel.HEARTBEAT], channel_kwargs={Channel.PRICES: { "granularity": 60 }}, cancel_position_on_exit=True, ) assert mock_client.call_count == 1 assert mock_client.call_args[1]["api_secret"] is None assert mock_client.call_args[1]["env"] == Environment.PROD assert set(mock_client.call_args[1]["channels"]) == set( BcexInterface.REQUIRED_CHANNELS + [Channel.HEARTBEAT]) assert mock_client.call_args[1]["cancel_position_on_exit"] is True
def __init__( self, symbol, api_key=None, env=Environment.STAGING, refresh_rate=5, channels_kwargs=None, ): self.exchange = BcexInterface( [symbol], api_secret=api_key, env=env, channels=self.CHANNELS, channel_kwargs=channels_kwargs, ) self.exchange.connect() self._symbol = symbol self._refresh_rate = refresh_rate # TODO: might want to extend the cancellation of open orders to symbol specific? self.reset()
def test_check_available_balance(self, mock_client): exi = BcexInterface(symbols=[Symbol.BTCUSD]) assert mock_client.call_count == 1 exi.client.balances.update({ "BTC": { "available": 1 }, "USD": { "available": 1000 } }) ins_details = { "symbol": "BTC-USD", "base_currency": "BTC", "base_currency_scale": 8, "counter_currency": "USD", } assert exi._check_available_balance(ins_details, OrderSide.BUY, 0.5, 10000) assert not exi._check_available_balance(ins_details, OrderSide.BUY, 5, 10000) assert not exi._check_available_balance(ins_details, OrderSide.SELL, 0.5, 10000) assert exi._check_available_balance(ins_details, OrderSide.SELL, 0.09, 10000)
def test_place_order(self, mock_client): exi = BcexInterface(symbols=[Symbol.ETHBTC]) assert mock_client.call_count == 1 exi.client.send_order = Mock() # no symbol details available exi.place_order(Symbol.BTCPAX, 100, 10.3) assert exi.client.send_order.call_count == 0 # symbol available exi.client.symbol_details[Symbol.BTCPAX] = { "min_price_increment": 10, "min_price_increment_scale": 2, "base_currency_scale": 2, "min_order_size": 50, "min_order_size_scale": 2, "max_order_size": 500, "max_order_size_scale": 2, } exi.place_order( Symbol.BTCPAX, OrderSide.SELL, quantity=1.01, price=1000.001, order_type=OrderType.LIMIT, post_only=True, ) assert exi.client.send_order.call_count == 1 act_order = exi.client.send_order.call_args[0][0] assert act_order is not None assert act_order.order_quantity == 1.01 assert act_order.price == 1000 assert act_order.post_only is True # symbol available but wrong parameters : too big quantity exi.client.send_order.reset_mock() exi.place_order( Symbol.BTCPAX, OrderSide.SELL, quantity=1000000000000000.113, price=1000.001, order_type=OrderType.LIMIT, ) assert exi.client.send_order.call_count == 0
def __init__(self, currency, code): super().__init__(currency) self.code = code self.update_rate = 30 self._client = BcexInterface(symbols=[code],env=Environment.PROD, channels=[Channel.L2]) self._client.connect()
def quote_randomly_both_sides_interface(ex_interface: BcexInterface, levels, sleep_time): """ Parameters ---------- bcex_client: BcexInterface levels: int levels of the orderbooks to populate sleep_time: int waiting time between each order updates in seconds """ ex_interface.connect() time.sleep(sleep_time) ex_interface.cancel_all_orders() while True: time.sleep(sleep_time) # Randomly cancel existing open orders for clordid, order in ex_interface.get_all_open_orders( to_dict=False).items(): # if isinstance(order, Order): # # exchange has not updated us on this order yet # continue if order.order_status not in OrderStatus.terminal_states(): if random.random() > 0.4: ex_interface.cancel_order(order_id=order.order_id) for symbol in ex_interface.get_symbols(): last_trade_price = ex_interface.get_last_traded_price(symbol) logging.info(f"Last Traded price {last_trade_price}") if last_trade_price: balance_coin = symbol.split("-")[1] balance = ex_interface.get_available_balance(balance_coin) logging.info(f"Balance {balance} {balance_coin}") if balance > 0: for k in range(levels): i = 1 + (k + 1) / 1000 + (random.random() - 0.5) / 1000 price = float(last_trade_price) / i order_quantity = min( max(balance / (price), 10 / price), 0.01) ex_interface.place_order( symbol=symbol, side=OrderSide.BUY, quantity=order_quantity, price=price, order_type=OrderType.LIMIT, time_in_force=TimeInForce.GTC, check_balance=True, ) logging.info( "Sending Buy Limit at {} amount {}".format( price, order_quantity)) balance_coin = symbol.split("-")[0] balance = ex_interface.get_available_balance(balance_coin) logging.info(f"Balance {balance} {balance_coin}") if balance > 0: for k in range(levels): i = 1 - (k + 1) / 1000 + (random.random() - 0.5) / 1000 price = round(float(last_trade_price / i), 0) order_quantity = min(round(balance / 10, 6), 0.01) ex_interface.place_order( symbol=symbol, side=OrderSide.SELL, quantity=order_quantity, price=price, order_type=OrderType.LIMIT, time_in_force=TimeInForce.GTC, check_balance=True, ) logging.info( "Sending Sell Limit at {} amount {}".format( price, order_quantity))
def test_create_order(self, mock_client): exi = BcexInterface(symbols=[Symbol.ETHBTC]) assert mock_client.call_count == 1
def test_scale_quantity(self): ins_details = { "base_currency_scale": 2, } assert BcexInterface._scale_quantity(ins_details, 0.01) == 0.01 assert BcexInterface._scale_quantity(ins_details, 0.011) == 0.01
def __init__(self): super().__init__() self.api_secret = config.bcex_api_secret self.get_info() self._client = BcexInterface(symbols=["BTC-USD"], env=Environment.PROD) self._client.connect()