Example #1
0
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}
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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
Example #5
0
 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
Example #6
0
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)
Example #7
0
    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()
Example #8
0
 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)
Example #9
0
    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
Example #10
0
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
Example #11
0
    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
Example #12
0
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"])
Example #13
0
    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
Example #14
0
    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()
Example #15
0
    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)
Example #16
0
    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
Example #17
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()
Example #18
0
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))
Example #19
0
 def test_create_order(self, mock_client):
     exi = BcexInterface(symbols=[Symbol.ETHBTC])
     assert mock_client.call_count == 1
Example #20
0
 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
Example #21
0
 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()