def _check_for_liquidations(candle: np.ndarray, exchange: str, symbol: str) -> None: p: Position = selectors.get_position(exchange, symbol) if not p: return # for now, we only support the isolated mode: if p.mode != 'isolated': return if candle_includes_price(candle, p.liquidation_price): closing_order_side = jh.closing_side(p.type) # create the market order that is used as the liquidation order order = Order({ 'id': jh.generate_unique_id(), 'symbol': symbol, 'exchange': exchange, 'side': closing_order_side, 'type': order_types.MARKET, 'flag': order_flags.REDUCE_ONLY, 'qty': jh.prepare_qty(p.qty, closing_order_side), 'price': p.bankruptcy_price, 'role': order_roles.CLOSE_POSITION }) store.orders.add_order(order) store.app.total_liquidations += 1 logger.info(f'{p.symbol} liquidated at {p.liquidation_price}') order.execute()
def _on_updated_position(self, order: Order) -> None: """ Handles the after-effect of the executed order Note that it assumes that the position has already been affected by the executed order. Arguments: order {Order} -- the executed order object """ role = order.role if role == order_roles.OPEN_POSITION and abs(self.position.qty) != abs( order.qty): order.role = order_roles.INCREASE_POSITION role = order_roles.INCREASE_POSITION if role == order_roles.CLOSE_POSITION and self.position.is_open: order.role = order_roles.REDUCE_POSITION role = order_roles.REDUCE_POSITION self._log_position_update(order, role) if role == order_roles.OPEN_POSITION: self._on_open_position(order) elif role == order_roles.CLOSE_POSITION and order in self._take_profit_orders: self._on_take_profit(order) elif role == order_roles.CLOSE_POSITION and order in self._stop_loss_orders: self._on_stop_loss(order) elif role == order_roles.INCREASE_POSITION: self._on_increased_position(order) elif role == order_roles.REDUCE_POSITION: self._on_reduced_position(order)
def fake_order(attributes=None): if attributes is None: attributes = {} global first_timestamp first_timestamp += 60000 exchange = exchanges.SANDBOX symbol = 'BTCUSD' side = sides.BUY order_type = order_types.LIMIT price = randint(40, 100) qty = randint(1, 10) status = order_statuses.ACTIVE created_at = first_timestamp return Order({ "id": jh.generate_unique_id(), 'symbol': attributes.get('symbol', symbol), 'exchange': attributes.get('exchange', exchange), 'side': attributes.get('side', side), 'type': attributes.get('type', order_type), 'qty': attributes.get('qty', qty), 'price': attributes.get('price', price), 'status': attributes.get('status', status), 'created_at': attributes.get('created_at', created_at), })
def stop_order(self, symbol, qty, price, side, role, flags): """ :param symbol: :param qty: :param price: :param side: :param role: :param flags: :return: """ order = Order({ 'id': jh.generate_unique_id(), 'symbol': symbol, 'exchange': self.name, 'side': side, 'type': order_types.STOP, 'flag': self.get_exec_inst(flags), 'qty': jh.prepare_qty(qty, side), 'price': price, 'role': role }) store.orders.add_order(order) return order
def market_order(self, symbol, qty, current_price, side, role, flags): """ :param symbol: :param qty: :param current_price: :param side: :param role: :param flags: :return: """ order = Order({ 'id': jh.generate_unique_id(), 'symbol': symbol, 'exchange': self.name, 'side': side, 'type': order_types.MARKET, 'flag': self.get_exec_inst(flags), 'qty': jh.prepare_qty(qty, side), 'price': current_price, 'role': role }) store.orders.add_order(order) store.orders.to_execute.append(order) return order
def test_cancel_order(): order = Order({ 'id': jh.generate_unique_id(), 'symbol': 'BTCUSD', 'type': order_types.LIMIT, 'price': 129.33, 'qty': 10.2041, 'side': sides.BUY, 'status': order_statuses.ACTIVE, 'created_at': jh.now(), }) assert order.is_canceled is False order.cancel() assert order.is_canceled is True assert order.canceled_at == jh.now()
def test_cancel_order(): set_up() order = Order({ 'id': jh.generate_unique_id(), 'exchange': 'Sandbox', 'symbol': 'BTC-USDT', 'type': order_types.LIMIT, 'price': 129.33, 'qty': 10.2041, 'side': sides.BUY, 'status': order_statuses.ACTIVE, 'created_at': jh.now_to_timestamp(), }) assert order.is_canceled is False order.cancel() assert order.is_canceled is True assert order.canceled_at == jh.now_to_timestamp()
def test_execute_order(): set_up_without_fee() order = Order({ 'id': jh.generate_unique_id(), 'symbol': 'BTC-USDT', 'exchange': exchange.name, 'type': order_types.LIMIT, 'price': 129.33, 'qty': 10.2041, 'side': sides.BUY, 'status': order_statuses.ACTIVE, 'created_at': jh.now_to_timestamp(), }) assert order.is_executed is False assert order.executed_at is None order.execute() assert order.is_executed is True assert order.executed_at == jh.now_to_timestamp()
def _on_updated_position(self, order: Order) -> None: """ Handles the after-effect of the executed order Note that it assumes that the position has already been affected by the executed order. Arguments: order {Order} -- the executed order object """ # in live-mode, sometimes order-update effects and new execution has overlaps, so: self._is_handling_updated_order = True role = order.role # if the order's role is CLOSE_POSITION but the position qty is not the same as this order's qty, # then it's increase_position order (because the position was already open before this) if self.trade and role == order_roles.OPEN_POSITION and abs( self.position.qty) != abs(order.qty): order.role = order_roles.INCREASE_POSITION role = order_roles.INCREASE_POSITION # if the order's role is CLOSE_POSITION but the position is still open, then it's reduce_position order if role == order_roles.CLOSE_POSITION and self.position.is_open: order.role = order_roles.REDUCE_POSITION role = order_roles.REDUCE_POSITION self._log_position_update(order, role) if role == order_roles.OPEN_POSITION: self._on_open_position(order) elif role == order_roles.CLOSE_POSITION: self._on_close_position(order) elif role == order_roles.INCREASE_POSITION: self._on_increased_position(order) elif role == order_roles.REDUCE_POSITION: self._on_reduced_position(order) self._is_handling_updated_order = False
def limit_order(self, symbol, qty, price, side, role, flags): order = Order({ 'id': jh.generate_unique_id(), 'symbol': symbol, 'exchange': self.name, 'side': side, 'type': order_types.LIMIT, 'flag': self.get_exec_inst(flags), 'qty': jh.prepare_qty(qty, side), 'price': price, 'role': role }) store.orders.add_order(order) return order
def stop_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order: order = Order({ 'id': jh.generate_unique_id(), 'symbol': symbol, 'exchange': self.name, 'side': side, 'type': order_types.STOP, 'flag': self.get_exec_inst(flags), 'qty': jh.prepare_qty(qty, side), 'price': price, 'role': role }) store.orders.add_order(order) return order
def market_order(self, symbol: str, qty: float, current_price: float, side: str, role: str, flags: list) -> Order: order = Order({ 'id': jh.generate_unique_id(), 'symbol': symbol, 'exchange': self.name, 'side': side, 'type': order_types.MARKET, 'flag': self.get_exec_inst(flags), 'qty': jh.prepare_qty(qty, side), 'price': current_price, 'role': role }) store.orders.add_order(order) store.orders.to_execute.append(order) return order
def _log_position_update(self, order: Order, role: str) -> None: """ A log can be either about opening, adding, reducing, or closing the position. Arguments: order {order} -- the order object """ # set the trade_id for the order if we're in the middle of a trade. Otherwise, it # is done at order_roles.OPEN_POSITION if self.trade: order.trade_id = self.trade.id if role == order_roles.OPEN_POSITION: self.trade = CompletedTrade() self.trade.leverage = self.leverage self.trade.orders = [order] self.trade.timeframe = self.timeframe self.trade.id = jh.generate_unique_id() order.trade_id = self.trade.id self.trade.strategy_name = self.name self.trade.exchange = order.exchange self.trade.symbol = order.symbol self.trade.type = trade_types.LONG if order.side == sides.BUY else trade_types.SHORT self.trade.qty = order.qty self.trade.opened_at = jh.now_to_timestamp() self.trade.entry_candle_timestamp = self.current_candle[0] elif role == order_roles.INCREASE_POSITION: self.trade.orders.append(order) self.trade.qty += order.qty elif role == order_roles.REDUCE_POSITION: self.trade.orders.append(order) self.trade.qty += order.qty elif role == order_roles.CLOSE_POSITION: self.trade.exit_candle_timestamp = self.current_candle[0] self.trade.orders.append(order) # calculate average stop-loss price sum_price = 0 sum_qty = 0 if self._log_stop_loss is not None: for l in self._log_stop_loss: sum_qty += abs(l[0]) sum_price += abs(l[0]) * l[1] self.trade.stop_loss_at = sum_price / sum_qty else: self.trade.stop_loss_at = np.nan # calculate average take-profit price sum_price = 0 sum_qty = 0 if self._log_take_profit is not None: for l in self._log_take_profit: sum_qty += abs(l[0]) sum_price += abs(l[0]) * l[1] self.trade.take_profit_at = sum_price / sum_qty else: self.trade.take_profit_at = np.nan # calculate average entry_price price sum_price = 0 sum_qty = 0 for l in self.trade.orders: if not l.is_executed: continue if jh.side_to_type(l.side) != self.trade.type: continue sum_qty += abs(l.qty) sum_price += abs(l.qty) * l.price self.trade.entry_price = sum_price / sum_qty # calculate average exit_price sum_price = 0 sum_qty = 0 for l in self.trade.orders: if not l.is_executed: continue if jh.side_to_type(l.side) == self.trade.type: continue sum_qty += abs(l.qty) sum_price += abs(l.qty) * l.price self.trade.exit_price = sum_price / sum_qty self.trade.closed_at = jh.now_to_timestamp() self.trade.qty = pydash.sum_by( filter(lambda o: o.side == jh.type_to_side(self.trade.type), self.trade.orders), lambda o: abs(o.qty)) store.completed_trades.add_trade(self.trade) if jh.is_livetrading(): store_completed_trade_into_db(self.trade) self.trade = None self.trades_count += 1 if jh.is_livetrading(): store_order_into_db(order)