def _fill_order(self, order: OrderCommon, qty_to_fill, price_fill, fee=0.): assert price_fill >= 0, "Price has to be positive" assert abs(order.leaves_qty ) >= 1, "Order with leaves_qty < 1 should be closed" assert abs(order.signed_qty ) >= 1, "Order signed_qty should not be less than 1" assert abs(qty_to_fill) >= 1, "Can not fill order with qty less than 1" fully_filled = order.fill(qty_to_fill) side = order.side() fill = Fill( symbol=order.symbol, side=order.side_str(), qty_filled=qty_to_fill, price_fill=price_fill, fill_time=self.current_timestamp, fill_type=FillType.complete if fully_filled else FillType.partial, order_id=order.client_id) self._log_fill(fill) self.volume[order.symbol] += abs(qty_to_fill) * price_fill self.n_fills[order.symbol] += 1 position = self.positions[order.symbol] position.update(signed_qty=qty_to_fill * side, price=price_fill, leverage=self.leverage[order.symbol], current_timestamp=self.current_timestamp, fee=fee) method = self._get_tactic(order.client_id).handle_fill self._queue_append(self.current_timestamp + WS_DELAY, method, fill) return fully_filled
def _exec_market_order(self, order: OrderCommon): assert order.type == OrderType.Market assert order.status == OrderStatus.Pending order.status = OrderStatus.New tick_size = order.symbol.tick_size # TODO: watch for self-cross # We only fill at two price levels: best price and second best price if order.side() > 0: best_price = self.current_quote.ask_price second_best = best_price + tick_size size = self.current_quote.ask_size else: best_price = self.current_quote.bid_price second_best = best_price - tick_size size = self.current_quote.bid_size qty_fill1 = min(size, order.leaves_qty) qty_fill2 = order.leaves_qty - qty_fill1 fully_filled = self._fill_order(order=order, qty_to_fill=qty_fill1, price_fill=best_price) if not fully_filled: assert qty_fill2 > 0 self._fill_order(order=order, qty_to_fill=qty_fill2, price_fill=second_best) return
def _exec_limit_order(self, order: OrderCommon): assert order.type == OrderType.Limit bid = self.current_quote.bid_price ask = self.current_quote.ask_price order_side = order.side() self._check_price_sanity(order) violated_post_only = order_side > 0 and order.price >= ask or order_side < 0 and order.price <= bid if violated_post_only: order.status = OrderStatus.Canceled order.status_msg = OrderCancelReason.cross_during_post_only self._exec_order_cancels([order]) return order.status = OrderStatus.New self.active_orders[order.client_id] = order order._made_spread = order_side > 0 and order.price > bid or order_side < 0 and order.price < ask
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