Esempio n. 1
0
    def get_ticker(self):
        ticker = self.exchange.get_ticker()
        tickLog = self.exchange.get_instrument()['tickLog']
        # Set up our buy & sell positions as the smallest possible unit above and below the current spread
        # and we'll work out from there. That way we always have the best price but we don't kill wide
        # and potentially profitable spreads.
        self.start_position_buy = ticker["buy"] + self.instrument['tickSize']
        self.start_position_sell = ticker["sell"] - self.instrument['tickSize']
        # If we're maintaining spreads and we already have orders in place,
        # make sure they're not ours. If they are, we need to adjust, otherwise we'll
        # just work the orders inward until they collide.
        if settings.MAINTAIN_SPREADS:
            if ticker['buy'] == self.exchange.get_highest_buy()['price']:
                self.start_position_buy = ticker["buy"]
            if ticker['sell'] == self.exchange.get_lowest_sell()['price']:
                self.start_position_sell = ticker["sell"]
        # Back off if our spread is too small.
        if self.start_position_buy * (
                1.00 + settings.MIN_SPREAD) > self.start_position_sell:
            self.start_position_buy *= (1.00 - (settings.MIN_SPREAD / 2))
            self.start_position_sell *= (1.00 + (settings.MIN_SPREAD / 2))

        # Midpoint, used for simpler order placement.
        self.start_position_mid = ticker["mid"]
        logger.info("%s Ticker: Buy: %.*f, Sell: %.*f" %
                    (self.instrument['symbol'], tickLog, ticker["buy"],
                     tickLog, ticker["sell"]))
        logger.info(
            'Start Positions: Buy: %.*f, Sell: %.*f, Mid: %.*f' %
            (tickLog, self.start_position_buy, tickLog,
             self.start_position_sell, tickLog, self.start_position_mid))
        return ticker
Esempio n. 2
0
def run():
    logger.info('BitMEX Market Maker Version: %s\n' % constants.VERSION)
    om = OrderManager(DummyExchangeInterface, [])
    try:
        om.run_loop()
    except (KeyboardInterrupt, SystemExit):
        sys.exit()
 def close_positions(self, symbol=None):
     if symbol is None:
         symbol = self.symbol
     position = self.bitmex.position(symbol)
     current_qty_ = position['currentQty']
     logger.info("closing positions. Net position is : ")
     logger.info(current_qty_)
     if current_qty_ != 0:
         return self.bitmex.place_order(quantity=-current_qty_,
                                        ordType="Market")
Esempio n. 4
0
def run():
    logger.info('BitMEX Market Maker Version: %s\n' % constants.VERSION)
    watched_files_mtimes = [(f, getmtime(f)) for f in settings.WATCHED_FILES]
    exchange = ExchangeInterface(settings.DRY_RUN)
    om = OrderManager(exchange, watched_files_mtimes)
    # Try/except just keeps ctrl-c from printing an ugly stacktrace
    try:
        om.run_loop()
    except (KeyboardInterrupt, SystemExit):
        sys.exit()
 def cancel_order(self, order):
     tickLog = self.get_instrument()['tickLog']
     logger.info(
         "Canceling: %s %d @ %.*f" %
         (order['side'], order['orderQty'], tickLog, order['price']))
     while True:
         try:
             self.bitmex.cancel(order['orderID'])
             sleep(settings.API_REST_INTERVAL)
         except ValueError as e:
             logger.info(e)
             sleep(settings.API_ERROR_INTERVAL)
         else:
             break
Esempio n. 6
0
    def sanity_check(self):
        """Perform checks before placing orders."""

        # Check if OB is empty - if so, can't quote.
        self.exchange.check_if_orderbook_empty()

        # Ensure market is still open.
        self.exchange.check_market_open()

        # Get ticker, which sets price offsets and prints some debugging info.
        ticker = self.get_ticker()

        # Sanity check:
        if self.get_price_offset(-1) >= ticker[
                "sell"] or self.get_price_offset(1) <= ticker["buy"]:
            logger.error("Buy: %s, Sell: %s" %
                         (self.start_position_buy, self.start_position_sell))
            logger.error(
                "First buy position: %s\nBitMEX Best Ask: %s\nFirst sell position: %s\nBitMEX Best Bid: %s"
                % (self.get_price_offset(-1), ticker["sell"],
                   self.get_price_offset(1), ticker["buy"]))
            logger.error("Sanity check failed, exchange data is inconsistent")
            self.exit()

        # Messaging if the position limits are reached
        if self.long_position_limit_exceeded([]):
            logger.info("Long delta limit exceeded")
            logger.info("Current Position: %.f, Maximum Position: %.f" %
                        (self.exchange.get_delta(), settings.MAX_POSITION))

        if self.short_position_limit_exceeded([]):
            logger.info("Short delta limit exceeded")
            logger.info("Current Position: %.f, Minimum Position: %.f" %
                        (self.exchange.get_delta(), settings.MIN_POSITION))
    def cancel_all_orders(self):
        if self.dry_run:
            return

        logger.info(
            "Resetting current position. Canceling all existing orders.")
        tickLog = self.get_instrument()['tickLog']

        # In certain cases, a WS update might not make it through before we call this.
        # For that reason, we grab via HTTP to ensure we grab them all.
        orders = self.bitmex.http_open_orders()

        for order in orders:
            logger.info(
                "Canceling: %s %d @ %.*f" %
                (order['side'], order['orderQty'], tickLog, order['price']))

        if len(orders):
            self.bitmex.cancel([order['orderID'] for order in orders])

        sleep(settings.API_REST_INTERVAL)
Esempio n. 8
0
    def place_orders(self):
        """Create order items for use in convergence."""

        buy_orders = []
        sell_orders = []
        buyAgressive = sellAgressive = False
        position = self.exchange.get_delta()
        if position != 0:
            if position < 0:
                buyAgressive = True
            else:
                sellAgressive = True
        # Create orders from the outside in. This is intentional - let's say the inner order gets taken;
        # then we match orders from the outside in, ensuring the fewest number of orders are amended and only
        # a new order is created in the inside. If we did it inside-out, all orders would be amended
        # down and a new order would be created at the outside.
            logger.info(settings.ORDER_PAIRS)
        logger.info("order pairs")
        for i in reversed(range(1, settings.ORDER_PAIRS + 1)):
            if not self.long_position_limit_exceeded(buy_orders):
                logger.info(buy_orders)
                buy_orders.append(self.prepare_order(-i, buyAgressive))
            if not self.short_position_limit_exceeded(sell_orders):
                sell_orders.append(self.prepare_order(i, sellAgressive))

        return self.converge_orders(buy_orders, sell_orders)
Esempio n. 9
0
    def exit(self):
        logger.info("Shutting down. All open orders will be cancelled.")
        try:
            self.exchange.cancel_all_orders()
            self.exchange.close_positions()
            self.exchange.bitmex.exit()
        except errors.AuthenticationError as e:
            logger.info("Was not authenticated; could not cancel orders.")
        except Exception as e:
            logger.info("Unable to cancel orders: %s" % e)

        sys.exit()
 def fill_position(self, qty, price, order):
     if self.position == 0:
         self.entryPrice = price
         self.entryTime = self.current_time
     else:
         print(
             str(self.entryTime) + "," + str(qty * ((1 / self.entryPrice) -
                                                    (1 / price))))
     logger.info("Initially margin was: %d, Position was: %d" %
                 (self.marginCashBalance, self.position))
     self.position = qty + self.position
     self.marginCashBalance += (qty * self.multiplicationFactor) / price
     logger.info("Quantity was: %d, Price was: %d" % (qty, price))
     logger.info("Finally margin was: %d, Position was: %d" %
                 (self.marginCashBalance, self.position))
     order['orderQty'] = order['orderQty'] - abs(qty)
Esempio n. 11
0
    def __init__(self, exchange, files):
        self.exchange = exchange
        self.watched_files_mtimes = files
        # Once exchange is created, register exit handler that will always cancel orders
        # on any error.
        atexit.register(self.exit)
        signal.signal(signal.SIGTERM, self.exit)

        logger.info("Using symbol %s." % self.exchange.symbol)

        if settings.DRY_RUN:
            logger.info(
                "Initializing dry run. Orders printed below represent what would be posted to BitMEX."
            )
        else:
            logger.info(
                "Order Manager initializing, connecting to BitMEX. Live run: executing real trades."
            )

        self.start_time = datetime.now()
        self.instrument = self.exchange.get_instrument()
        self.starting_qty = self.exchange.get_delta()
        self.running_qty = self.starting_qty
        self.reset()
Esempio n. 12
0
    def print_status(self):
        """Print the current MM status."""

        margin = self.exchange.get_margin()
        position = self.exchange.get_position()
        self.running_qty = self.exchange.get_delta()
        tickLog = self.exchange.get_instrument()['tickLog']
        self.start_XBt = margin["marginBalance"]

        logger.info("Current XBT Balance: %.6f" % XBt_to_XBT(self.start_XBt))
        logger.info("Current Contract Position: %d" % self.running_qty)
        if settings.CHECK_POSITION_LIMITS:
            logger.info("Position limits: %d/%d" %
                        (settings.MIN_POSITION, settings.MAX_POSITION))
        if position['currentQty'] != 0:
            logger.info("Avg Cost Price: %.*f" %
                        (tickLog, float(position['avgCostPrice'])))
            logger.info("Avg Entry Price: %.*f" %
                        (tickLog, float(position['avgEntryPrice'])))
        logger.info("Contracts Traded This Run: %d" %
                    (self.running_qty - self.starting_qty))
        logger.info("Total Contract Delta: %.4f XBT" %
                    self.exchange.calc_delta()['spot'])
Esempio n. 13
0
 def restart(self):
     logger.info("Restarting the market maker...")
     os.execv(sys.executable, [sys.executable] + sys.argv)
Esempio n. 14
0
    def converge_orders(self, buy_orders, sell_orders):
        """Converge the orders we currently have in the book with what we want to be in the book.
           This involves amending any open orders and creating new ones if any have filled completely.
           We start from the closest orders outward."""

        tickLog = self.exchange.get_instrument()['tickLog']
        to_amend = []
        to_create = []
        to_cancel = []
        buys_matched = 0
        sells_matched = 0
        existing_orders = self.exchange.get_orders()
        # Check all existing orders and match them up with what we want to place.
        # If there's an open one, we might be able to amend it to fit what we want.
        for order in existing_orders:
            try:
                if order['side'] == 'Buy':
                    desired_order = buy_orders[buys_matched]
                    buys_matched += 1
                else:
                    desired_order = sell_orders[sells_matched]
                    sells_matched += 1

                # Found an existing order. Do we need to amend it?
                if desired_order['orderQty'] != order['leavesQty'] or (
                        # If price has changed, and the change is more than our RELIST_INTERVAL, amend.
                        desired_order['price'] != order['price']
                        and abs((desired_order['price'] / order['price']) - 1)
                        > settings.RELIST_INTERVAL):
                    to_amend.append({
                        'orderID':
                        order['orderID'],
                        'orderQty':
                        order['cumQty'] + desired_order['orderQty'],
                        'price':
                        desired_order['price'],
                        'side':
                        order['side']
                    })
            except IndexError:
                # Will throw if there isn't a desired order to match. In that case, cancel it.
                to_cancel.append(order)

        while buys_matched < len(buy_orders):
            to_create.append(buy_orders[buys_matched])
            buys_matched += 1

        while sells_matched < len(sell_orders):
            to_create.append(sell_orders[sells_matched])
            sells_matched += 1

        if len(to_amend) > 0:
            for amended_order in reversed(to_amend):
                reference_order = [
                    o for o in existing_orders
                    if o['orderID'] == amended_order['orderID']
                ][0]
                logger.info(
                    "Amending %4s: %d @ %.*f to %d @ %.*f (%+.*f)" %
                    (amended_order['side'], reference_order['leavesQty'],
                     tickLog, reference_order['price'],
                     (amended_order['orderQty'] - reference_order['cumQty']),
                     tickLog, amended_order['price'], tickLog,
                     (amended_order['price'] - reference_order['price'])))
            # This can fail if an order has closed in the time we were processing.
            # The API will send us `invalid ordStatus`, which means that the order's status (Filled/Canceled)
            # made it not amendable.
            # If that happens, we need to catch it and re-tick.
            try:
                self.exchange.amend_bulk_orders(to_amend)
            except requests.exceptions.HTTPError as e:
                errorObj = e.response.json()
                if errorObj['error']['message'] == 'Invalid ordStatus':
                    logger.warn(
                        "Amending failed. Waiting for order data to converge and retrying."
                    )
                    self.exchange.sleep(0.5)
                    return self.place_orders()
                else:
                    logger.error("Unknown error on amend: %s. Exiting" %
                                 errorObj)
                    sys.exit(1)

        if len(to_create) > 0:
            logger.info("Creating %d orders:" % (len(to_create)))
            logger.info(
                "----------------------------------------------------------------------"
            )
            logger.info(to_create)
            for order in reversed(to_create):
                logger.info("%4s %d @ %.*f" %
                            (order['side'], order['orderQty'], tickLog,
                             order['price']))
            self.exchange.create_bulk_orders(to_create)

        # Could happen if we exceed a delta limit
        if len(to_cancel) > 0:
            logger.info("Canceling %d orders:" % (len(to_cancel)))
            for order in reversed(to_cancel):
                logger.info("%4s %d @ %.*f" %
                            (order['side'], order['leavesQty'], tickLog,
                             order['price']))
            self.exchange.cancel_bulk_orders(to_cancel)