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)
def _execute_order(self, order: OrderCommon) -> Fill: assert self.tick_num < len(self.ohlcv) if order.status != OrderStatus.Pending and not order.is_open(): raise ValueError("expected order to be opened, but got " + str(order.status) + ". Order = \n" + order.get_header() + "\n" + str(order)) current_candle = self.current_candle() current_time = current_candle.name # pd.Timestamp high = current_candle.high low = current_candle.low open = current_candle.open close = current_candle.close position = self.positions[order.symbol] # type: PositionSim current_qty = position.signed_qty qty_fill = qty_to_close = outstanding_qty = None crossed = False if order.type is OrderType.Market: crossed = True price_fill = self._estimate_price() qty_fill = order.signed_qty elif order.type is OrderType.Limit: price_fill = order.price max_qty_fill = order.leaves_qty * order.side() # clip fill if (open <= order.price <= close) or (close <= order.price <= open): qty_fill = max_qty_fill elif high == low == order.price: qty_fill = round(0.5 * max_qty_fill) else: if low < order.price < high: if order.is_sell(): factor = max((high - order.price) / (high - low), 0.50001) assert factor >= 0 else: factor = max((low - order.price) / (low - high), 0.50001) assert factor >= 0 qty_fill = round(factor * max_qty_fill) if qty_fill is not None: crossed = True else: raise ValueError("order type " + str(order.type) + " not supported") if not crossed: return None # type: Fill if position.is_open and position.would_change_side(qty_fill): qty_to_close = float(sign(qty_fill)) * min(abs(current_qty), abs(qty_fill)) outstanding_qty = qty_fill - qty_to_close if order.fill(qty_fill) or order.type == OrderType.Market: order.status = OrderStatus.Filled order.fill_price = price_fill if order.price is not None and ((open <= order.price <= close) or (close <= order.price <= open)): assert order.status == OrderStatus.Filled fee = self.FEE[order.type] if outstanding_qty: position = self._update_position(order.symbol, qty=qty_to_close, price=price_fill, leverage=self.leverage[order.symbol], current_timestamp=current_time, fee=fee) assert not position.is_open position = self._update_position(order.symbol, qty=outstanding_qty, price=price_fill, leverage=self.leverage[order.symbol], current_timestamp=current_time, fee=fee) assert position.is_open else: self._update_position(order.symbol, qty=qty_fill, price=price_fill, leverage=self.leverage[order.symbol], current_timestamp=current_time, fee=fee) fill = Fill(order=order, qty_filled=qty_fill, price_fill=price_fill, fill_time=current_time, fill_type=FillType.complete if (order.status == OrderStatus.Filled) else FillType.partial) self.fills_hist += [fill] self.active_orders = drop_closed_orders_dict(self.active_orders) if self.can_call_handles: order.tactic.handle_fill(fill) return fill