def on_fill_actions(self): self.taker_or_maker = ExchangeConstantsMarketPropertyColumns.TAKER.value self.origin_price = self.created_last_price self.filled_price = self.created_last_price self.filled_quantity = self.origin_quantity self.total_cost = self.filled_price * self.filled_quantity Order.on_fill_actions(self)
async def cancel_order(self, order: Order, is_cancelled_from_exchange: bool = False, ignored_order: Order = None): """ Cancels the given order and its linked orders, and updates the portfolio, publish in order channel if order is from a real exchange. :param order: Order to cancel :param is_cancelled_from_exchange: When True, will not try to cancel this order on real exchange :param ignored_order: Order not to cancel if found in linked orders recursive cancels (ex: avoid cancelling a filled order) :return: None """ if order and not order.is_cancelled() and not order.is_filled(): async with order.lock: # always cancel this order first to avoid infinite loop followed by deadlock order.cancel_order() for linked_order in order.linked_orders: if linked_order is not ignored_order: await self.cancel_order(linked_order, ignored_order=ignored_order) raw_cancelled_order = await self._handle_order_cancellation( order, is_cancelled_from_exchange) self.logger.info( f"{order.symbol} {order.get_name()} at {order.origin_price}" f" (ID : {order.order_id}) cancelled on {self.exchange_manager.exchange_name}" ) # nothing to update in case of a simulated order if raw_cancelled_order is not None: await self.exchange_manager.exchange_personal_data.handle_order_update_from_raw( order.symbol, order.order_id, raw_cancelled_order)
async def test_parse_order_type(self): _, exchange_manager, trader_inst = await self.init_default() order_to_test = Order(trader_inst) ccxt_order_buy_market = { "side": TradeOrderSide.BUY, "type": TradeOrderType.MARKET } order_to_test.update_from_raw(ccxt_order_buy_market) assert order_to_test.order_type == TraderOrderType.BUY_MARKET ccxt_order_buy_limit = { "side": TradeOrderSide.BUY, "type": TradeOrderType.LIMIT } assert parse_order_type(ccxt_order_buy_limit) == ( TradeOrderSide.BUY, TraderOrderType.BUY_LIMIT) ccxt_order_sell_market = { "side": TradeOrderSide.SELL, "type": TradeOrderType.MARKET } assert parse_order_type(ccxt_order_sell_market) == ( TradeOrderSide.SELL, TraderOrderType.SELL_MARKET) ccxt_order_sell_limit = { "side": TradeOrderSide.SELL, "type": TradeOrderType.LIMIT } assert parse_order_type(ccxt_order_sell_limit) == ( TradeOrderSide.SELL, TraderOrderType.SELL_LIMIT) await self.stop(exchange_manager)
async def _create_not_loaded_order(self, new_order: Order, portfolio) -> Order: """ Creates an exchange managed order """ if not self.simulate and not new_order.is_self_managed(): created_order = await self.exchange_manager.exchange.create_order( new_order.order_type, new_order.symbol, new_order.origin_quantity, new_order.origin_price, new_order.origin_stop_price) self.logger.info( f"Created order on {self.exchange_manager.exchange_name}: {created_order}" ) # get real order from exchange new_order = Order(self) new_order.update_from_raw(created_order) # rebind linked portfolio to new order instance new_order.linked_portfolio = portfolio # update the availability of the currency in the portfolio portfolio.update_portfolio_available(new_order, is_new_order=True) return new_order
def clear(self): if self.wait_for_hit_event_task is not None: if not self.limit_price_hit_event.is_set(): self.wait_for_hit_event_task.cancel() self.wait_for_hit_event_task = None if self.limit_price_hit_event is not None: self.exchange_manager.exchange_symbols_data. \ get_exchange_symbol_data(self.symbol).price_events_manager.remove_event(self.limit_price_hit_event) Order.clear(self)
async def test_simulated_update(trader_simulator): config, exchange_manager_inst, trader_inst = trader_simulator order_sim_inst = Order(trader_inst) order_sim_inst.update(order_type=TraderOrderType.SELL_MARKET, symbol="LTC/USDT", quantity=100, price=3.22) assert order_sim_inst.status == OrderStatus.OPEN assert order_sim_inst.filled_quantity == order_sim_inst.origin_quantity == 100
async def cancel_order(self, order: Order): if order and not order.is_cancelled() and not order.is_filled(): async with order.lock: odr = order cancelled_order = await odr.cancel_order() self.logger.info( f"{odr.symbol} {odr.get_name()} at {odr.origin_price}" f" (ID : {odr.order_id}) cancelled on {self.exchange_manager.exchange.name}" ) if cancelled_order: await self.exchange_manager.exchange_personal_data.handle_order_update( order.symbol, order.order_id, cancelled_order)
async def _handle_order_cancellation( self, order: Order, is_cancelled_from_exchange: bool) -> dict: raw_cancelled_order = None # if real order: cancel on exchange if not self.simulate and not order.is_self_managed( ) and not is_cancelled_from_exchange: raw_cancelled_order = await self.exchange_manager.exchange.cancel_order( order.order_id, order.symbol) # add to trade history and notify await self.exchange_manager.exchange_personal_data.handle_trade_instance_update( create_trade_from_order(order, close_status=OrderStatus.CANCELED, canceled_time=self.exchange_manager. exchange.get_exchange_current_time())) # update portfolio with cancelled funds order async with self.exchange_manager.exchange_personal_data.get_order_portfolio( order).lock: self.exchange_manager.exchange_personal_data.get_order_portfolio(order) \ .update_portfolio_available(order, is_new_order=False) # remove order from open_orders self.exchange_manager.exchange_personal_data.orders_manager.remove_order_instance( order) return raw_cancelled_order
async def test_update(trader): config, exchange_manager_inst, trader_inst = trader # with real trader order_inst = Order(trader_inst) order_inst.update(order_type=TraderOrderType.BUY_MARKET, symbol="BTC/USDT", current_price=10000, quantity=1) assert order_inst.order_type == TraderOrderType.BUY_MARKET assert order_inst.symbol == "BTC/USDT" assert order_inst.created_last_price == 10000 assert order_inst.origin_quantity == 1 assert order_inst.creation_time != 0 assert order_inst.get_currency_and_market() == ('BTC', 'USDT') assert order_inst.side is None assert order_inst.status == OrderStatus.OPEN assert order_inst.filled_quantity != order_inst.origin_quantity order_inst.update(order_type=TraderOrderType.STOP_LOSS_LIMIT, symbol="ETH/BTC", quantity=0.1, quantity_filled=5.2, price=0.12, stop_price=0.9) assert order_inst.origin_stop_price == 0.9 assert order_inst.origin_price == 0.12
async def cancel_order(self, order: Order): if order and not order.is_cancelled() and not order.is_filled(): async with order.lock: odr = order cancelled_order = await odr.cancel_order() self.logger.info( f"{odr.symbol} {odr.get_name()} at {odr.origin_price}" f" (ID : {odr.order_id}) cancelled on {self.exchange_manager.exchange_name}" ) # skip_upsert when cancelled_order is None (nothing to update because simulated order) skip_upsert = cancelled_order is None cancelled_order = odr if skip_upsert else cancelled_order await self.exchange_manager.exchange_personal_data.handle_order_update( order.symbol, order.order_id, cancelled_order, skip_upsert=skip_upsert)
async def cancel_order(self, order: Order, ignored_order: Order = None): """ Cancels the given order and its linked orders, and updates the portfolio, publish in order channel if order is from a real exchange. :param order: Order to cancel :param ignored_order: Order not to cancel if found in linked orders recursive cancels (ex: avoid cancelling a filled order) :return: None """ if order and order.is_open(): # always cancel this order first to avoid infinite loop followed by deadlock await self._handle_order_cancellation(order, ignored_order)
async def _create_new_order(self, new_order: Order, portfolio) -> Order: """ Creates an exchange managed order, it might be a simulated or a real order. Then updates the portfolio. """ if not self.simulate and not new_order.is_self_managed(): created_order = await self.exchange_manager.exchange.create_order( new_order.order_type, new_order.symbol, new_order.origin_quantity, new_order.origin_price, new_order.origin_stop_price) self.logger.info( f"Created order on {self.exchange_manager.exchange_name}: {created_order}" ) # get real order from exchange new_order = create_order_instance_from_raw(self, created_order, force_open=True) # rebind linked portfolio to new order instance new_order.linked_portfolio = portfolio return new_order
async def test_parse_exchange_order_to_trade_instance(self): _, exchange_manager, trader_inst = await self.init_default() timestamp = time.time() order_to_test = Order(trader_inst) exchange_order = { "status": OrderStatus.PARTIALLY_FILLED.value, "symbol": self.DEFAULT_SYMBOL, # "fee": 0.001, "price": 10.1444215411, "cost": 100.1444215411, "filled": 1.568415145687741563132, "timestamp": timestamp } order_to_test.update_from_raw(exchange_order) assert order_to_test.status == OrderStatus.PARTIALLY_FILLED assert order_to_test.filled_quantity == 1.568415145687741563132 assert order_to_test.filled_price == 10.1444215411 # assert order_to_test.fee == 0.001 assert order_to_test.total_cost == 100.1444215411 await self.stop(exchange_manager)
async def _handle_order_cancellation(self, order: Order, ignored_order: Order): success = True async with order.lock: # if real order: cancel on exchange if not self.simulate and not order.is_self_managed(): success = await self.exchange_manager.exchange.cancel_order( order.order_id, order.symbol) if not success: # retry to cancel order success = await self.exchange_manager.exchange.cancel_order( order.order_id, order.symbol) if not success: self.logger.error(f"Failed to cancel order {order}") else: order.status = OrderStatus.CLOSED self.logger.debug(f"Successfully cancelled order {order}") else: order.status = OrderStatus.CANCELED # call CancelState termination await order.on_cancel(force_cancel=success, is_from_exchange_data=False, ignored_order=ignored_order)
def clear(self): """ Clear prices hit events and their related tasks and call super clear """ self._clear_event_and_tasks() Order.clear(self)
def _create_trade_from_raw(self, raw_trade): order = Order(self.trader) order.order_id = raw_trade[ExchangeConstantsOrderColumns.ID.value] order.origin_price = raw_trade[ ExchangeConstantsOrderColumns.PRICE.value] order.origin_quantity = raw_trade[ ExchangeConstantsOrderColumns.AMOUNT.value] order.symbol = raw_trade[ExchangeConstantsOrderColumns.SYMBOL.value] order.currency, order.market = self.exchange_manager.get_exchange_quote_and_base( raw_trade[ExchangeConstantsOrderColumns.SYMBOL.value]) order.filled_quantity = raw_trade[ ExchangeConstantsOrderColumns.AMOUNT.value] order.filled_quantity = None # TODO order.filled_price = None # TODO order.total_cost = None # TODO order.order_type = None # TODO order.fee = None # TODO order.side = None # TODO order.canceled_time = None # TODO order.executed_time = None # TODO return Trade(order)
async def test_get_profitability(trader_simulator): config, exchange_manager_inst, trader_inst = trader_simulator # Test filled_price > create_last_price # test side SELL order_filled_sup_side_sell_inst = Order(trader_inst) order_filled_sup_side_sell_inst.side = TradeOrderSide.SELL order_filled_sup_side_sell_inst.filled_price = 10 order_filled_sup_side_sell_inst.created_last_price = 9 assert order_filled_sup_side_sell_inst.get_profitability() == (-(1 - 10 / 9)) # test side BUY order_filled_sup_side_sell_inst = Order(trader_inst) order_filled_sup_side_sell_inst.side = TradeOrderSide.BUY order_filled_sup_side_sell_inst.filled_price = 15.114778 order_filled_sup_side_sell_inst.created_last_price = 7.265 assert order_filled_sup_side_sell_inst.get_profitability() == (1 - 15.114778 / 7.265) # Test filled_price < create_last_price # test side SELL order_filled_sup_side_sell_inst = Order(trader_inst) order_filled_sup_side_sell_inst.side = TradeOrderSide.SELL order_filled_sup_side_sell_inst.filled_price = 11.556877 order_filled_sup_side_sell_inst.created_last_price = 20 assert order_filled_sup_side_sell_inst.get_profitability() == (1 - 20 / 11.556877) # test side BUY order_filled_sup_side_sell_inst = Order(trader_inst) order_filled_sup_side_sell_inst.side = TradeOrderSide.BUY order_filled_sup_side_sell_inst.filled_price = 8 order_filled_sup_side_sell_inst.created_last_price = 14.35 assert order_filled_sup_side_sell_inst.get_profitability() == (-(1 - 14.35 / 8)) # Test filled_price == create_last_price # test side SELL order_filled_sup_side_sell_inst = Order(trader_inst) order_filled_sup_side_sell_inst.side = TradeOrderSide.SELL order_filled_sup_side_sell_inst.filled_price = 1517374.4567 order_filled_sup_side_sell_inst.created_last_price = 1517374.4567 assert order_filled_sup_side_sell_inst.get_profitability() == 0 # test side BUY order_filled_sup_side_sell_inst = Order(trader_inst) order_filled_sup_side_sell_inst.side = TradeOrderSide.BUY order_filled_sup_side_sell_inst.filled_price = 0.4275587387858527 order_filled_sup_side_sell_inst.created_last_price = 0.4275587387858527 assert order_filled_sup_side_sell_inst.get_profitability() == 0
def _create_order_from_raw(self, raw_order): order = Order(self.trader) order.update_from_raw(raw_order) return order
def update_close_orders(self): for symbol in self.exchange_manager.exchange_config.get_traded_pairs(): for close_order in self.exchange_manager.get_closed_orders(symbol): self.parse_exchange_order_to_trade_instance( close_order, Order(self))