Exemplo n.º 1
0
def websocket_to_order_book():
    try:
        websocket = yield from websockets.connect("wss://ws-feed.exchange.coinbase.com")
    except gaierror:
        file_logger.error('socket.gaierror - had a problem connecting to Coinbase feed')
        return
    yield from websocket.send('{"type": "subscribe", "product_id": "BTC-USD"}')

    messages = []
    while True:
        message = yield from websocket.recv()
        messages += [message]
        if len(messages) > 50:
            break

    order_book.get_level3()
    open_orders.get_open_orders()

    [process_message(message) for message in messages]

    while True:
        message = yield from websocket.recv()
        if not process_message(message):
            print(pformat(message))
            return False
        if not manage_orders():
            print(pformat(message))
            return False
def buyer_strategy(order_book, open_orders, spreads):
    time.sleep(10)
    while True:
        time.sleep(0.001)
        if not open_orders.open_bid_order_id:
            open_bid_price = order_book.bids.price_tree.max_key() - spreads.bid_spread
            if 0.01 * float(open_bid_price) < float(open_orders.accounts['USD']['available']):
                order = {'size': '0.01',
                         'price': str(open_bid_price),
                         'side': 'buy',
                         'product_id': 'BTC-USD',
                         'post_only': True}
                response = requests.post(exchange_api_url + 'orders', json=order, auth=exchange_auth)
                try:
                    response = response.json()
                except ValueError:
                    file_logger.error('Unhandled response: {0}'.format(pformat(response)))
                if 'status' in response and response['status'] == 'pending':
                    open_orders.open_bid_order_id = response['id']
                    open_orders.open_bid_price = open_bid_price
                    open_orders.open_bid_rejections = Decimal('0.0')
                    file_logger.info('new bid @ {0}'.format(open_bid_price))
                elif 'status' in response and response['status'] == 'rejected':
                    open_orders.open_bid_order_id = None
                    open_orders.open_bid_price = None
                    open_orders.open_bid_rejections += Decimal('0.04')
                    file_logger.warn('rejected: new bid @ {0}'.format(open_bid_price))
                elif 'message' in response and response['message'] == 'Insufficient funds':
                    open_orders.open_bid_order_id = None
                    open_orders.open_bid_price = None
                    file_logger.warn('Insufficient USD')
                elif 'message' in response and response['message'] == 'request timestamp expired':
                    open_orders.open_bid_order_id = None
                    open_orders.open_bid_price = None
                    file_logger.warn('Request timestamp expired')
                else:
                    file_logger.error('Unhandled response: {0}'.format(pformat(response)))
                continue

        if open_orders.open_bid_order_id and not open_orders.open_bid_cancelled:
            bid_too_far_out = open_orders.open_bid_price < (order_book.bids.price_tree.max_key()
                                                            - spreads.bid_too_far_adjustment_spread)
            bid_too_close = open_orders.open_bid_price > (order_book.bids.price_tree.max_key()
                                                          - spreads.bid_too_close_adjustment_spread)
            cancel_bid = bid_too_far_out or bid_too_close
            if cancel_bid:
                if bid_too_far_out:
                    file_logger.info('CANCEL: open bid {0} too far from best bid: {1} spread: {2}'.format(
                        open_orders.open_bid_price,
                        order_book.bids.price_tree.max_key(),
                        order_book.bids.price_tree.max_key() - open_orders.open_bid_price))
                if bid_too_close:
                    file_logger.info('CANCEL: open bid {0} too close to best bid: {1} spread: {2}'.format(
                        open_orders.open_bid_price,
                        order_book.bids.price_tree.max_key(),
                        order_book.bids.price_tree.max_key() - open_orders.open_bid_price))
                open_orders.cancel('bid')
                continue
Exemplo n.º 3
0
 def cancel(self, side):
     if side == 'bid':
         order_id = self.open_bid_order_id
         price = self.open_bid_price
         self.open_bid_cancelled = True
     elif side == 'ask':
         order_id = self.open_ask_order_id
         price = self.open_ask_price
         self.open_ask_cancelled = True
     else:
         return False
     response = requests.delete(exchange_api_url + 'orders/' + str(order_id), auth=exchange_auth)
     if response.status_code == 200:
         file_logger.info('canceled {0} {1} @ {2}'.format(side, order_id, price))
     elif 'message' in response.json() and response.json()['message'] == 'order not found':
         file_logger.info('{0} already canceled: {1} @ {2}'.format(side, order_id, price))
     elif 'message' in response.json() and response.json()['message'] == 'Order already done':
         file_logger.info('{0} already filled: {1} @ {2}'.format(side, order_id, price))
     else:
         file_logger.error('Unhandled response: {0}'.format((pformat(response.json()))))
    def process_message(self, message):

        new_sequence = int(message["sequence"])

        if new_sequence <= self.level3_sequence:
            return True

        if not self.first_sequence:
            self.first_sequence = new_sequence
            self.last_sequence = new_sequence
            assert new_sequence - self.level3_sequence == 1
        else:
            if (new_sequence - self.last_sequence) != 1:
                file_logger.error("sequence gap: {0}".format(new_sequence - self.last_sequence))
                return False
            self.last_sequence = new_sequence

        if "order_type" in message and message["order_type"] == "market":
            return True

        message_type = message["type"]
        message_time = parse(message["time"])
        self.last_time = message_time
        side = message["side"]

        if message_type == "received" and side == "buy":
            self.bids.receive(message["order_id"], message["size"])
            return True
        elif message_type == "received" and side == "sell":
            self.asks.receive(message["order_id"], message["size"])
            return True

        elif message_type == "open" and side == "buy":
            self.bids.insert_order(message["order_id"], Decimal(message["remaining_size"]), Decimal(message["price"]))
            return True
        elif message_type == "open" and side == "sell":
            self.asks.insert_order(message["order_id"], Decimal(message["remaining_size"]), Decimal(message["price"]))
            return True

        elif message_type == "match" and side == "buy":
            self.bids.match(message["maker_order_id"], Decimal(message["size"]))
            self.matches.appendleft((message_time, side, Decimal(message["size"]), Decimal(message["price"])))
            return True
        elif message_type == "match" and side == "sell":
            self.asks.match(message["maker_order_id"], Decimal(message["size"]))
            self.matches.appendleft((message_time, side, Decimal(message["size"]), Decimal(message["price"])))
            return True

        elif message_type == "done" and side == "buy":
            self.bids.remove_order(message["order_id"])
            return True
        elif message_type == "done" and side == "sell":
            self.asks.remove_order(message["order_id"])
            return True

        elif message_type == "change" and side == "buy":
            self.bids.change(message["order_id"], Decimal(message["new_size"]))
            return True
        elif message_type == "change" and side == "sell":
            self.asks.change(message["order_id"], Decimal(message["new_size"]))
            return True

        else:
            file_logger.error("Unhandled message: {0}".format(pformat(message)))
            return False
def market_maker_strategy(open_orders, order_book, spreads):
    time.sleep(10)
    open_orders.get_open_orders()
    open_orders.cancel_all()
    while True:
        time.sleep(0.005)
        if order_book.asks.price_tree.min_key() - order_book.bids.price_tree.max_key() < 0:
            file_logger.warn('Negative spread: {0}'.format(
                order_book.asks.price_tree.min_key() - order_book.bids.price_tree.max_key()))
            continue
        if not open_orders.open_bid_order_id:
            open_bid_price = order_book.asks.price_tree.min_key() - spreads.bid_spread - open_orders.open_bid_rejections
            if 0.01 * float(open_bid_price) < float(open_orders.accounts['USD']['available']):
                order = {'size': '0.01',
                         'price': str(open_bid_price),
                         'side': 'buy',
                         'product_id': 'BTC-USD',
                         'post_only': True}
                response = requests.post(exchange_api_url + 'orders', json=order, auth=exchange_auth)
                if 'status' in response.json() and response.json()['status'] == 'pending':
                    open_orders.open_bid_order_id = response.json()['id']
                    open_orders.open_bid_price = open_bid_price
                    open_orders.open_bid_rejections = Decimal('0.0')
                    file_logger.info('new bid @ {0}'.format(open_bid_price))
                elif 'status' in response.json() and response.json()['status'] == 'rejected':
                    open_orders.open_bid_order_id = None
                    open_orders.open_bid_price = None
                    open_orders.open_bid_rejections += Decimal('0.04')
                    file_logger.warn('rejected: new bid @ {0}'.format(open_bid_price))
                elif 'message' in response.json() and response.json()['message'] == 'Insufficient funds':
                    open_orders.open_bid_order_id = None
                    open_orders.open_bid_price = None
                    file_logger.warn('Insufficient USD')
                else:
                    file_logger.error('Unhandled response: {0}'.format(pformat(response.json())))
                continue

        if not open_orders.open_ask_order_id:
            open_ask_price = order_book.bids.price_tree.max_key() + spreads.ask_spread + open_orders.open_ask_rejections
            if 0.01 < float(open_orders.accounts['BTC']['available']):
                order = {'size': '0.01',
                         'price': str(open_ask_price),
                         'side': 'sell',
                         'product_id': 'BTC-USD',
                         'post_only': True}
                response = requests.post(exchange_api_url + 'orders', json=order, auth=exchange_auth)
                if 'status' in response.json() and response.json()['status'] == 'pending':
                    open_orders.open_ask_order_id = response.json()['id']
                    open_orders.open_ask_price = open_ask_price
                    file_logger.info('new ask @ {0}'.format(open_ask_price))
                    open_orders.open_ask_rejections = Decimal('0.0')
                elif 'status' in response.json() and response.json()['status'] == 'rejected':
                    open_orders.open_ask_order_id = None
                    open_orders.open_ask_price = None
                    open_orders.open_ask_rejections += Decimal('0.04')
                    file_logger.warn('rejected: new ask @ {0}'.format(open_ask_price))
                elif 'message' in response.json() and response.json()['message'] == 'Insufficient funds':
                    open_orders.open_ask_order_id = None
                    open_orders.open_ask_price = None
                    file_logger.warn('Insufficient BTC')
                else:
                    file_logger.error('Unhandled response: {0}'.format(pformat(response.json())))
                continue

        if open_orders.open_bid_order_id and not open_orders.open_bid_cancelled:
            bid_too_far_out = open_orders.open_bid_price < (order_book.asks.price_tree.min_key()
                                                            - spreads.bid_too_far_adjustment_spread)
            bid_too_close = open_orders.open_bid_price > (order_book.bids.price_tree.max_key()
                                                          - spreads.bid_too_close_adjustment_spread)
            cancel_bid = bid_too_far_out or bid_too_close
            if cancel_bid:
                if bid_too_far_out:
                    file_logger.info('CANCEL: open bid {0} too far from best ask: {1} spread: {2}'.format(
                        open_orders.open_bid_price,
                        order_book.asks.price_tree.min_key(),
                        open_orders.open_bid_price - order_book.asks.price_tree.min_key()))
                if bid_too_close:
                    file_logger.info('CANCEL: open bid {0} too close to best bid: {1} spread: {2}'.format(
                        open_orders.open_bid_price,
                        order_book.bids.price_tree.max_key(),
                        open_orders.open_bid_price - order_book.bids.price_tree.max_key()))
                open_orders.cancel('bid')
                continue

        if open_orders.open_ask_order_id and not open_orders.open_ask_cancelled:
            ask_too_far_out = open_orders.open_ask_price > (order_book.bids.price_tree.max_key() +
                                                            spreads.ask_too_far_adjustment_spread)

            ask_too_close = open_orders.open_ask_price < (order_book.asks.price_tree.min_key() -
                                                          spreads.ask_too_close_adjustment_spread)

            cancel_ask = ask_too_far_out or ask_too_close

            if cancel_ask:
                if ask_too_far_out:
                    file_logger.info('CANCEL: open ask {0} too far from best bid: {1} spread: {2}'.format(
                        open_orders.open_ask_price,
                        order_book.bids.price_tree.max_key(),
                        open_orders.open_ask_price - order_book.bids.price_tree.max_key()))
                if ask_too_close:
                    file_logger.info('CANCEL: open ask {0} too close to best ask: {1} spread: {2}'.format(
                        open_orders.open_ask_price,
                        order_book.asks.price_tree.min_key(),
                        open_orders.open_ask_price - order_book.asks.price_tree.min_key()))
                open_orders.cancel('ask')
                continue
Exemplo n.º 6
0
    def process_message(self, message):

        new_sequence = int(message['sequence'])

        if new_sequence <= self.level3_sequence:
            return True

        if not self.first_sequence:
            self.first_sequence = new_sequence
            self.last_sequence = new_sequence
            assert new_sequence - self.level3_sequence == 1
        else:
            if (new_sequence - self.last_sequence) != 1:
                file_logger.error(
                    'sequence gap: {0}'.format(new_sequence -
                                               self.last_sequence))
                return False
            self.last_sequence = new_sequence

        if 'order_type' in message and message['order_type'] == 'market':
            return True

        message_type = message['type']
        message_time = parse(message['time'])
        self.last_time = message_time
        side = message['side']

        if message_type == 'received' and side == 'buy':
            self.bids.receive(message['order_id'], message['size'])
            return True
        elif message_type == 'received' and side == 'sell':
            self.asks.receive(message['order_id'], message['size'])
            return True

        elif message_type == 'open' and side == 'buy':
            self.bids.insert_order(message['order_id'],
                                   Decimal(message['remaining_size']),
                                   Decimal(message['price']))
            return True
        elif message_type == 'open' and side == 'sell':
            self.asks.insert_order(message['order_id'],
                                   Decimal(message['remaining_size']),
                                   Decimal(message['price']))
            return True

        elif message_type == 'match' and side == 'buy':
            self.bids.match(message['maker_order_id'],
                            Decimal(message['size']))
            self.matches.appendleft(
                (message_time, side, Decimal(message['size']),
                 Decimal(message['price'])))
            return True
        elif message_type == 'match' and side == 'sell':
            self.asks.match(message['maker_order_id'],
                            Decimal(message['size']))
            self.matches.appendleft(
                (message_time, side, Decimal(message['size']),
                 Decimal(message['price'])))
            return True

        elif message_type == 'done' and side == 'buy':
            self.bids.remove_order(message['order_id'])
            return True
        elif message_type == 'done' and side == 'sell':
            self.asks.remove_order(message['order_id'])
            return True

        elif message_type == 'change' and side == 'buy':
            self.bids.change(message['order_id'], Decimal(message['new_size']))
            return True
        elif message_type == 'change' and side == 'sell':
            self.asks.change(message['order_id'], Decimal(message['new_size']))
            return True

        else:
            file_logger.error('Unhandled message: {0}'.format(
                pformat(message)))
            return False
Exemplo n.º 7
0
def process_message(message):
    if message is None:
        file_logger.error('Websocket message is None.')
        return False

    try:
        message = json.loads(message)
    except TypeError:
        file_logger.error('JSON did not load, see ' + str(message))
        return False

    new_sequence = int(message['sequence'])

    if new_sequence <= order_book.level3_sequence:
        return True

    if not order_book.first_sequence:
        order_book.first_sequence = new_sequence
        order_book.last_sequence = new_sequence
        file_logger.info('Gap between level 3 and first message: {0}'
                         .format(new_sequence - order_book.level3_sequence))
        assert new_sequence - order_book.level3_sequence == 1
    else:
        if (new_sequence - order_book.last_sequence - 1) != 0:
            file_logger.error('sequence gap: {0}'.format(new_sequence - order_book.last_sequence))
            return False
        order_book.last_sequence = new_sequence

    if 'order_type' in message and message['order_type'] == 'market':
        return True

    message_type = message['type']
    message_time = parse(message['time'])
    order_book.last_time = message_time
    side = message['side']

    if message_type == 'received' and side == 'buy':
        order_book.bids.receive(message['order_id'], message['size'])
        return True
    elif message_type == 'received' and side == 'sell':
        order_book.asks.receive(message['order_id'], message['size'])
        return True

    elif message_type == 'open' and side == 'buy':
        order_book.bids.insert_order(message['order_id'], Decimal(message['remaining_size']), Decimal(message['price']))
        return True
    elif message_type == 'open' and side == 'sell':
        order_book.asks.insert_order(message['order_id'], Decimal(message['remaining_size']), Decimal(message['price']))
        return True

    elif message_type == 'match' and side == 'buy':
        order_book.bids.match(message['maker_order_id'], Decimal(message['size']))
        order_book.matches.appendleft((message_time, side, Decimal(message['size']), Decimal(message['price'])))
        return True
    elif message_type == 'match' and side == 'sell':
        order_book.asks.match(message['maker_order_id'], Decimal(message['size']))
        order_book.matches.appendleft((message_time, side, Decimal(message['size']), Decimal(message['price'])))
        return True

    elif message_type == 'done' and side == 'buy':
        order_book.bids.remove_order(message['order_id'])
        if message['order_id'] == open_orders.open_bid_order_id:
            if message['reason'] == 'filled':
                file_logger.info('bid filled @ {0}'.format(open_orders.open_bid_price))
            open_orders.open_bid_order_id = None
            open_orders.open_bid_price = None
            open_orders.insufficient_btc = False
        return True
    elif message_type == 'done' and side == 'sell':
        order_book.asks.remove_order(message['order_id'])
        if message['order_id'] == open_orders.open_ask_order_id:
            if message['reason'] == 'filled':
                file_logger.info('ask filled @ {0}'.format(open_orders.open_ask_price))
            open_orders.open_ask_order_id = None
            open_orders.open_ask_price = None
            open_orders.insufficient_usd = False
        return True

    elif message_type == 'change' and side == 'buy':
        order_book.bids.change(message['order_id'], Decimal(message['new_size']))
        return True
    elif message_type == 'change' and side == 'sell':
        order_book.asks.change(message['order_id'], Decimal(message['new_size']))
        return True

    else:
        file_logger.error('Unhandled message: {0}'.format(pformat(message)))
        return False
Exemplo n.º 8
0
def manage_orders():
    max_bid = Decimal(order_book.bids.price_tree.max_key())
    min_ask = Decimal(order_book.asks.price_tree.min_key())
    if min_ask - max_bid < 0:
        file_logger.warn('Negative spread: {0}'.format(min_ask - max_bid))
        return False
    if command_line:
        print('Latency: {0:.6f} secs, '
              'Min ask: {1:.2f}, Max bid: {2:.2f}, Spread: {3:.2f}, '
              'Your ask: {4:.2f}, Your bid: {5:.2f}, Your spread: {6:.2f}'.format(
            ((datetime.now(tzlocal()) - order_book.last_time).microseconds * 1e-6),
            min_ask, max_bid, min_ask - max_bid,
            open_orders.float_open_ask_price, open_orders.float_open_bid_price,
                              open_orders.float_open_ask_price - open_orders.float_open_bid_price), end='\r')

    if not open_orders.open_bid_order_id and not open_orders.insufficient_usd:
        if open_orders.insufficient_btc:
            size = 0.1
            open_bid_price = Decimal(round(max_bid + Decimal(open_orders.open_bid_rejections), 2))
        else:
            size = 0.01
            spreads.bid_spread = Decimal(round((random.randrange(15) + 6) / 100, 2))
            open_bid_price = Decimal(round(min_ask - Decimal(spreads.bid_spread)
                                           - Decimal(open_orders.open_bid_rejections), 2))
        order = {'size': size,
                 'price': str(open_bid_price),
                 'side': 'buy',
                 'product_id': 'BTC-USD',
                 'post_only': True}
        response = requests.post(exchange_api_url + 'orders', json=order, auth=exchange_auth)
        if 'status' in response.json() and response.json()['status'] == 'pending':
            open_orders.open_bid_order_id = response.json()['id']
            open_orders.open_bid_price = open_bid_price
            open_orders.open_bid_rejections = 0.0
            file_logger.info('new bid @ {0}'.format(open_bid_price))
        elif 'status' in response.json() and response.json()['status'] == 'rejected':
            open_orders.open_bid_order_id = None
            open_orders.open_bid_price = None
            open_orders.open_bid_rejections += 0.04
            file_logger.warn('rejected: new bid @ {0}'.format(open_bid_price))
        elif 'message' in response.json() and response.json()['message'] == 'Insufficient funds':
            open_orders.insufficient_usd = True
            open_orders.open_bid_order_id = None
            open_orders.open_bid_price = None
            file_logger.warn('Insufficient USD')
        else:
            file_logger.error('Unhandled response: {0}'.format(pformat(response.json())))
            return False
        return True

    if not open_orders.open_ask_order_id and not open_orders.insufficient_btc:
        if open_orders.insufficient_usd:
            size = 0.10
            open_ask_price = Decimal(round(min_ask + Decimal(open_orders.open_ask_rejections), 2))
        else:
            size = 0.01
            spreads.ask_spread = Decimal(round((random.randrange(15) + 6) / 100, 2))
            open_ask_price = Decimal(round(max_bid + Decimal(spreads.ask_spread)
                                           + Decimal(open_orders.open_ask_rejections), 2))
        order = {'size': size,
                 'price': str(open_ask_price),
                 'side': 'sell',
                 'product_id': 'BTC-USD',
                 'post_only': True}
        response = requests.post(exchange_api_url + 'orders', json=order, auth=exchange_auth)
        if 'status' in response.json() and response.json()['status'] == 'pending':
            open_orders.open_ask_order_id = response.json()['id']
            open_orders.open_ask_price = open_ask_price
            file_logger.info('new ask @ {0}'.format(open_ask_price))
            open_orders.open_ask_rejections = 0
        elif 'status' in response.json() and response.json()['status'] == 'rejected':
            open_orders.open_ask_order_id = None
            open_orders.open_ask_price = None
            open_orders.open_ask_rejections += 0.04
            file_logger.warn('rejected: new ask @ {0}'.format(open_ask_price))
        elif 'message' in response.json() and response.json()['message'] == 'Insufficient funds':
            open_orders.insufficient_btc = True
            open_orders.open_ask_order_id = None
            open_orders.open_ask_price = None
            file_logger.warn('Insufficient BTC')
        else:
            file_logger.error('Unhandled response: {0}'.format(pformat(response.json())))
            return False
        return True

    if open_orders.open_bid_order_id and Decimal(open_orders.open_bid_price) < round(
                    min_ask - Decimal(spreads.bid_adjustment_spread), 2):
        file_logger.info('CANCEL: open bid {0} threshold {1} diff {2}'.format(
            Decimal(open_orders.open_bid_price),
            round(min_ask - Decimal(spreads.bid_adjustment_spread), 2),
            Decimal(open_orders.open_bid_price) - round(min_ask - Decimal(spreads.bid_adjustment_spread), 2)))
        open_orders.cancel('bid')
        return True

    if open_orders.open_ask_order_id and Decimal(open_orders.open_ask_price) > round(
                    max_bid + Decimal(spreads.ask_adjustment_spread), 2):
        file_logger.info('CANCEL: open ask {0} threshold {1} diff {2}'.format(
            Decimal(open_orders.open_ask_price),
            round(max_bid - Decimal(spreads.ask_adjustment_spread), 2),
            Decimal(open_orders.open_ask_price) - round(max_bid + Decimal(spreads.ask_adjustment_spread), 2)))
        open_orders.cancel('ask')
        return True
    return True
Exemplo n.º 9
0
        file_logger.info('CANCEL: open ask {0} threshold {1} diff {2}'.format(
            Decimal(open_orders.open_ask_price),
            round(max_bid - Decimal(spreads.ask_adjustment_spread), 2),
            Decimal(open_orders.open_ask_price) - round(max_bid + Decimal(spreads.ask_adjustment_spread), 2)))
        open_orders.cancel('ask')
        return True
    return True

if __name__ == '__main__':
    if len(sys.argv) == 1:
        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(logging.Formatter('\n%(asctime)s, %(levelname)s, %(message)s'))
        stream_handler.setLevel(logging.INFO)
        file_logger.addHandler(stream_handler)
        command_line = True

    loop = asyncio.get_event_loop()
    n = 0
    while True:
        start_time = loop.time()
        loop.run_until_complete(websocket_to_order_book())
        end_time = loop.time()
        seconds = end_time - start_time
        if seconds < 2:
            n += 1
            sleep_time = (2 ** n) + (random.randint(0, 1000) / 1000)
            file_logger.error('Websocket connectivity problem, going to sleep for {0}'.format(sleep_time))
            time.sleep(sleep_time)
            if n > 6:
                n = 0