Beispiel #1
0
    def _create_orders(self, learning: float, fcst: float, quote: Quote):

        # TODO: improve to use full book information
        buy_best = quote.bid_price if quote.bid_size >= self.min_liq else quote.bid_price - 0.5
        sell_best = quote.ask_price if quote.ask_size >= self.min_liq else quote.ask_price + 0.5

        buy_best -= self.spread
        sell_best += self.spread

        price = fcst - learning / self.alpha

        buy = min(buy_best - 0.5, self._floor(price - 0.25))
        sell = max(sell_best + 0.5, self._ceil(price + 0.25))

        qty_buy = min(self.max_qty, round((price - buy) * self.alpha))
        qty_sel = min(self.max_qty, round((sell - price) * self.alpha))

        if qty_buy >= 1:
            self.buy_order = OrderCommon(symbol=self.symbol,
                                         type=OrderType.Limit,
                                         client_id=self.gen_order_id(),
                                         signed_qty=qty_buy,
                                         price=buy)

        if qty_sel >= 1:
            self.sell_order = OrderCommon(symbol=self.symbol,
                                          type=OrderType.Limit,
                                          client_id=self.gen_order_id(),
                                          signed_qty=-qty_sel,
                                          price=sell)
Beispiel #2
0
    def send_orders(self, orders: List[OrderCommon]):
        # DEV NOTE: it logs all sent orders, good or bad orders
        # DEV NOTE: it should NOT change any internal state

        # sanity check
        if not all([o.status == OrderStatus.Pending for o in orders]):
            raise ValueError(
                "New orders should have status OrderStatus.Pending")

        for o in orders:
            if o.client_id:
                tokens = o.client_id.split('_')
                tactic_id = tokens[0]
            if not o.client_id or len(
                    tokens) < 2 or tactic_id not in self.tactics_map:
                raise ValueError(
                    'The order client_id should be a string in the form TACTICID_HASH'
                )

        converted = [
            self.convert_order_to_bitmex(self.check_order_sanity(order))
            for order in orders
        ]
        raws = self._curl_bitmex(path='order/bulk',
                                 postdict={'orders': converted},
                                 verb='POST')

        for r in raws:
            symbol = Symbol[r['symbol']]
            type_ = OrderType[r['ordType']]
            tactic_id = r['clOrdID'].split('_')[0]
            tactic = self.tactics_map[tactic_id]
            self._log_order(OrderCommon(symbol, type_).update_from_bitmex(r))
Beispiel #3
0
 def _liq_pos(self, pos: float):
     self.exchange.send_orders([
         OrderCommon(symbol=self.symbol,
                     type=OrderType.Market,
                     client_id=self.gen_order_id(),
                     signed_qty=-pos)
     ])
Beispiel #4
0
    def _create_orders(self):
        quote = self.exchange.get_quote(self.symbol)
        bid = quote.bid_price
        ask = quote.ask_price

        # sending two orders at same time to check if we get two different fills
        self.orders_to_send = [
            OrderCommon(symbol=self.symbol,
                        type=OrderType.Limit,
                        price=bid - 0.5,
                        client_id=self.gen_order_id(),
                        signed_qty=+self.qty),
            OrderCommon(symbol=self.symbol,
                        type=OrderType.Limit,
                        price=ask + 0.5,
                        client_id=self.gen_order_id(),
                        signed_qty=-self.qty)
        ]
Beispiel #5
0
    def _create_orders(self):
        self.orders_to_send = []
        self.expected_position = []

        # step 0
        self.orders_to_send.append([
            OrderCommon(symbol=self.symbol,
                        type=OrderType.Market,
                        client_id=self.gen_my_id('a'),
                        signed_qty=-self.qty)
        ])
        self.expected_position.append(-self.qty)

        # step 1
        # sending two orders at same time to check if we get two different fills
        self.orders_to_send.append([
            OrderCommon(symbol=self.symbol,
                        type=OrderType.Market,
                        client_id=self.gen_my_id('b'),
                        signed_qty=-self.qty),
            OrderCommon(symbol=self.symbol,
                        type=OrderType.Market,
                        client_id=self.gen_my_id('c'),
                        signed_qty=-self.qty)
        ])
        self.expected_position.append(-3 * self.qty)

        # step 2
        self.orders_to_send.append([
            OrderCommon(symbol=self.symbol,
                        type=OrderType.Market,
                        client_id=self.gen_my_id('d'),
                        signed_qty=+4 * self.qty)
        ])
        self.expected_position.append(+self.qty)

        # step 3
        self.orders_to_send.append([
            OrderCommon(symbol=self.symbol,
                        type=OrderType.Market,
                        client_id=self.gen_my_id('e'),
                        signed_qty=-self.qty)
        ])
        self.expected_position.append(0)
Beispiel #6
0
    def on_order(self, raw: dict, action: str):
        # DEV NOTE: this is the only method who can delete orders from self.all_orders
        # DEV NOTE: this is the only method who broadcast orders to tactics

        order_id = raw.get('clOrdID')

        if not order_id:
            self.logger.error(
                'Got an order without "clOrdID", probably a bitmex order (e.g., liquidation)'
            )
            self.logger.error('order: {}'.format(raw))
            raise AttributeError(
                'If we were liquidated, we should change our tactics')

        if OrderStatus[raw['ordStatus']] == OrderStatus.Rejected:
            raise ValueError(
                'A order should never be rejected, please fix the tactic. Order: {}'
                .format(raw))

        order = self.all_orders.get(order_id)

        if order:
            if action != 'update':
                raise AttributeError(
                    'Got action "{}". This should be an "update", since this order already '
                    'exist in our data and we don\'t expect a "delete"'.format(
                        action))

            if not order.is_open():
                # This is the case when a closed (e.g. Canceled) order where inserted, now we clean it up in the insert
                # We also assume that bitmex will not update a canceled/closed order twice
                del self.all_orders[order.client_id]
                return

            order.update_from_bitmex(raw)  # type: OrderCommon

            if not order.is_open():
                del self.all_orders[order.client_id]

                self._notify_cancel_if_the_case(order)

        else:
            symbol = Symbol[raw['symbol']]
            type_ = OrderType[raw['ordType']]
            order = OrderCommon(symbol=symbol,
                                type=type_).update_from_bitmex(raw)

            if action != 'insert' and order.is_open():
                raise AttributeError(
                    'Got action "{}". This should be an "insert", since this data does\'nt exist '
                    'in memory and is open. Order: {}'.format(action, raw))

            # yes, always add an order to the list, even if it is closed. If so, it will be removed in the "update" feed
            self.all_orders[order_id] = order

            self._notify_cancel_if_the_case(order)
Beispiel #7
0
 def close_position(self, symbol: Symbol):
     # TODO: it should also close limit orders on the same side as the position
     pos = self.positions[symbol]
     oid = self.liquidator_tactic.gen_order_id().split('_')
     oid = oid[0] + '_closing_' + oid[1]
     if pos.is_open:
         self.send_orders([
             OrderCommon(symbol=symbol,
                         type=OrderType.Market,
                         client_id=oid,
                         signed_qty=-pos.signed_qty)
         ])
Beispiel #8
0
    def handle_1m_candles(self, candles1m: pd.DataFrame) -> None:

        if not self.sell_id:
            opened_orders = self.exchange.get_opened_orders(self.symbol, self.id())
            if len(opened_orders) != 0:
                raise ValueError('This test has to start with no opened orders')

            # wait for quotes
            time.sleep(0.1)

            price = self.exchange.get_quote(self.symbol).ask_price
            if not price:
                raise AttributeError("We should have quote by now")

            orders = [
                # First order should be accepted
                OrderCommon(symbol=self.symbol,
                            type=OrderType.Limit,
                            client_id=self.gen_order_id(),
                            signed_qty=-self.qty,
                            price=price + 1000),

                # First order should be cancelled because we will try to sell bellow the bid
                # Note that our Limit orders should be post-only, i.e., ParticipateDoNotInitiate
                OrderCommon(symbol=self.symbol,
                            type=OrderType.Limit,
                            client_id=self.gen_order_id(),
                            signed_qty=-self.qty,
                            price=max(price - 500, 1)),
            ]

            self.sell_id = [o.client_id for o in orders]

            self.exchange.send_orders(orders)

        pass
Beispiel #9
0
    def get_opened_orders(self, symbol: Symbol,
                          client_id_prefix: str) -> OrderContainerType:

        raws = self._curl_bitmex(path='order',
                                 query={
                                     'symbol': symbol.name,
                                     'filter': '{"open": true}'
                                 },
                                 verb='GET')

        def is_owner(client_id: str):
            return client_id.split('_')[0] == client_id_prefix

        return {
            r['clOrdID']:
            OrderCommon(symbol, OrderType[r['ordType']]).update_from_bitmex(r)
            for r in raws if r['leavesQty'] != 0 and is_owner(r['clOrdID'])
        }
Beispiel #10
0
    def handle_1m_candles(self, candles1m: pd.DataFrame) -> None:
        if not self.candle_last_ts:
            self.candle_last_ts = candles1m.index[-1]
        elif not isinstance(candles1m, str):
            assert self.candle_last_ts < candles1m.index[-1]

        if self.n_trades > 0 and not self.buy_id[0] and self.n_positions > 0:
            logger.info("opening a position")
            self.initial_pos = self.exchange.get_position(
                self.symbol).signed_qty

            self.buy_id[0] = self.gen_order_id()
            self.next_action = 1
            logger.info("sending buy order {}".format(self.buy_id[0]))
            self.exchange.send_orders([
                OrderCommon(symbol=self.symbol,
                            type=OrderType.Market,
                            client_id=self.buy_id[0],
                            signed_qty=+self.qty)
            ])

        pass
Beispiel #11
0
    def _exec_liquidation(self,
                          symbol: Symbol,
                          reason=OrderCancelReason.liquidation):
        orders_to_cancel = [
            o for o in self.active_orders.values() if o.symbol == symbol
        ]
        for order in orders_to_cancel:
            order.status_msg = reason
        self._exec_order_cancels(orders_to_cancel)

        if reason == OrderCancelReason.liquidation:
            self.n_liquidations[symbol] += 1

        if self.positions[symbol].is_open:
            order = OrderCommon(
                symbol=symbol,
                type=OrderType.Market,
                client_id=self.liquidator_tactic.gen_order_id(),
                signed_qty=-self.positions[symbol].signed_qty)

            self._exec_market_order(order)

        return
Beispiel #12
0
 def _execute_liquidation(self, symbol, order_cancel_reason=OrderCancelReason.liquidation):
     self.can_call_handles = False
     orders = filter_symbol(self.active_orders, symbol)
     cancelled = self._cancel_orders_helper(orders, reason=order_cancel_reason)
     self.active_orders = drop_orders(self.active_orders, cancelled)
     self.can_call_handles = True
     try:
         assert len(filter_symbol(self.active_orders, symbol)) == 0
     except:
         for i in filter_symbol(self.active_orders, symbol):
             print("-" + str(i[1].status))
         print("-" + str(len(cancelled)))
         raise AttributeError()
     position = self.positions.get(symbol, None)
     if not position or not position.is_open:
         return
     assert position.is_open
     order = OrderCommon(symbol=symbol,
                         signed_qty=-position.signed_qty,
                         type=OrderType.Market,
                         tactic=self.liquidator)
     order.status_msg = order_cancel_reason
     self.can_call_handles = False
     self.send_orders([order])
     self.can_call_handles = True
     assert order.status == OrderStatus.Filled
     if position.is_open:
         raise AttributeError("position was not close during liquidation. position = %f" % position.signed_qty)
     if not self.is_last_tick():
         self.n_liquidations[symbol.name] += 1
     if order_cancel_reason == OrderCancelReason.liquidation:
         closed = self.closed_positions_hist[symbol][-1]  # type: PositionSim
         if closed.realized_pnl >= 0:
             raise AttributeError("Liquidation caused profit! position = {},\n current price = {}"
                                  .format(str(position), self._estimate_price()))
     assert len(filter_symbol(self.active_orders, symbol)) == 0
Beispiel #13
0
    def handle_fill(self, fill: Fill) -> None:

        if fill.side.lower()[0] == 'b':
            i = self.buy_id_to_index(fill.order_id)
            if i == -1:
                raise AttributeError("We didn't send order with id {}".format(
                    fill.order_id))
            self.buy_leaves[i] -= fill.qty

        else:
            if fill.side.lower()[0] != 's':
                raise AttributeError(
                    'side should be buy or sell, got {}'.format(fill.side))

            i = self.sell_id_to_index(fill.order_id)
            if i == -1:
                raise AttributeError("We didn't send order with id {}".format(
                    fill.order_id))
            self.sell_leaves[i] -= fill.qty

        if fill.fill_type == FillType.complete:
            # we need this little delays because it seems that bitmex takes a while to update the position
            time.sleep(.3)
            pos = self.exchange.get_position(self.symbol)
            assertEqual(
                pos.signed_qty, self.expected_position(),
                "n_buys={}, n_sells={}, init_pos={}".format(
                    min(self.next_action, self.n_trades),
                    max(self.next_action - self.n_trades, 0),
                    self.initial_pos))

            if self.next_action < self.n_trades:

                client_id = self.gen_order_id()
                self.buy_id[self.next_action] = client_id
                logger.info("sending buy order {}".format(
                    self.buy_id[self.next_action]))
                self.next_action += 1
                self.exchange.send_orders([
                    OrderCommon(symbol=self.symbol,
                                type=OrderType.Market,
                                client_id=client_id,
                                signed_qty=+self.qty)
                ])

            elif self.n_trades <= self.next_action < 2 * self.n_trades:

                client_id = self.gen_order_id()
                self.sell_id[self.next_action - self.n_trades] = client_id
                self.next_action += 1
                logger.info("sending sell order {}".format(self.sell_id))
                self.exchange.send_orders([
                    OrderCommon(symbol=self.symbol,
                                type=OrderType.Market,
                                client_id=client_id,
                                signed_qty=-self.qty)
                ])
            else:
                time.sleep(.3)
                logger.info(
                    "checking position, it should be = initial position ...")
                pos = self.exchange.get_position(self.symbol)
                assertEqual(pos.signed_qty, self.initial_pos)
                self.n_closed_positions += 1

                pnls = self.exchange.get_pnl_history(self.symbol)
                if len(pnls) != self.n_closed_positions:
                    raise AttributeError(
                        "Expected to have {} closed position, got {}".format(
                            self.n_closed_positions, len(pnls)))

                self.n_positions -= 1
                if self.n_positions > 0:
                    self.__init__(self.n_trades, self.n_positions)
                    self.handle_1m_candles('skip_candle')

        pass