def test_market_became_wider(self): # TODO self.clock.backtest_til(self.start_timestamp + 5) bid_order: LimitOrder = self.strategy.active_bids[0][1] ask_order: LimitOrder = self.strategy.active_asks[0][1] self.assertEqual(Decimal("0.99452"), bid_order.price) self.assertEqual(Decimal("1.0056"), ask_order.price) self.assertEqual(Decimal("3.0"), bid_order.quantity) self.assertEqual(Decimal("3.0"), ask_order.quantity) self.taker_market.trigger_event( MarketEvent.BuyOrderCreated, BuyOrderCreatedEvent(self.start_timestamp + 5, OrderType.LIMIT, bid_order.trading_pair, bid_order.quantity, bid_order.price, bid_order.client_order_id)) self.taker_market.trigger_event( MarketEvent.SellOrderCreated, SellOrderCreatedEvent(self.start_timestamp + 5, OrderType.LIMIT, ask_order.trading_pair, ask_order.quantity, ask_order.price, ask_order.client_order_id)) self.simulate_order_book_widening(self.taker_data.order_book, 0.99, 1.01) self.clock.backtest_til(self.start_timestamp + 100) self.assertEqual(2, len(self.cancel_order_logger.event_log)) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) bid_order = self.strategy.active_bids[0][1] ask_order = self.strategy.active_asks[0][1] self.assertEqual(Decimal("0.98457"), bid_order.price) self.assertEqual(Decimal("1.0156"), ask_order.price)
def test_sell_order_created_event_creates_order_record(self): recorder = MarketsRecorder(sql=self.manager, markets=[self], config_file_path=self.config_file_path, strategy_name=self.strategy_name) event = SellOrderCreatedEvent( timestamp=int(time.time()), type=OrderType.LIMIT, trading_pair=self.trading_pair, amount=Decimal(1), price=Decimal(1000), order_id="OID1", creation_timestamp=1640001112.223, exchange_order_id="EOID1", ) recorder._did_create_order(MarketEvent.SellOrderCreated.value, self, event) with self.manager.get_new_session() as session: query = session.query(Order) orders = query.all() order = orders[0] order_status = order.status trade_fills = order.trade_fills self.assertEqual(1, len(orders)) self.assertEqual(self.config_file_path, orders[0].config_file_path) self.assertEqual(event.order_id, orders[0].id) self.assertEqual(1640001112223, orders[0].creation_timestamp) self.assertEqual(1, len(order_status)) self.assertEqual(MarketEvent.SellOrderCreated.name, order_status[0].status) self.assertEqual(0, len(trade_fills))
async def execute_sell( self, order_id: str, trading_pair: str, amount: Decimal, order_type: OrderType, price: Optional[Decimal] = Decimal("NaN"), ): try: await self.execute_order(TradeType.SELL, order_id, trading_pair, amount, order_type, price) tracked_order = self.in_flight_orders.get(order_id) if tracked_order is not None: self.trigger_event( SELL_ORDER_CREATED_EVENT, SellOrderCreatedEvent( now(), order_type, trading_pair, Decimal(amount), Decimal(price), order_id, tracked_order.creation_timestamp, ), ) except ValueError as e: # never tracked, so no need to stop tracking self.trigger_event( ORDER_FAILURE_EVENT, MarketOrderFailureEvent(now(), order_id, order_type)) self.logger().warning( f"Failed to place {order_id} on bitmex. {str(e)}")
def test_top_depth_tolerance(self): # TODO self.clock.remove_iterator(self.strategy) self.clock.add_iterator(self.strategy_with_top_depth_tolerance) self.clock.backtest_til(self.start_timestamp + 5) bid_order: LimitOrder = self.strategy_with_top_depth_tolerance.active_bids[ 0][1] ask_order: LimitOrder = self.strategy_with_top_depth_tolerance.active_asks[ 0][1] self.taker_market.trigger_event( MarketEvent.BuyOrderCreated, BuyOrderCreatedEvent( self.start_timestamp + 5, OrderType.LIMIT, bid_order.trading_pair, bid_order.quantity, bid_order.price, bid_order.client_order_id, bid_order.creation_timestamp * 1e-6, )) self.taker_market.trigger_event( MarketEvent.SellOrderCreated, SellOrderCreatedEvent( self.start_timestamp + 5, OrderType.LIMIT, ask_order.trading_pair, ask_order.quantity, ask_order.price, ask_order.client_order_id, ask_order.creation_timestamp * 1e-6, )) self.assertEqual(Decimal("0.99451"), bid_order.price) self.assertEqual(Decimal("1.0055"), ask_order.price) self.assertEqual(Decimal("3.0"), bid_order.quantity) self.assertEqual(Decimal("3.0"), ask_order.quantity) self.simulate_order_book_widening( self.taker_market.order_books[self.taker_trading_pairs[0]], 0.99, 1.01) self.clock.backtest_til(self.start_timestamp + 100) self.assertEqual(2, len(self.cancel_order_logger.event_log)) self.assertEqual( 1, len(self.strategy_with_top_depth_tolerance.active_bids)) self.assertEqual( 1, len(self.strategy_with_top_depth_tolerance.active_asks)) bid_order = self.strategy_with_top_depth_tolerance.active_bids[0][1] ask_order = self.strategy_with_top_depth_tolerance.active_asks[0][1] self.assertEqual(Decimal("0.98457"), bid_order.price) self.assertEqual(Decimal("1.0155"), ask_order.price)
async def execute_sell(self, order_id: str, trading_pair: str, amount: Decimal, order_type: OrderType, price: Optional[Decimal] = s_decimal_0): trading_rule = self._trading_rules[trading_pair] if not order_type.is_limit_type(): self.trigger_event( self.MARKET_ORDER_FAILURE_EVENT_TAG, MarketOrderFailureEvent(self.current_timestamp, order_id, order_type)) raise Exception(f"Unsupported order type: {order_type}") decimal_price = self.quantize_order_price(trading_pair, price) decimal_amount = self.quantize_order_amount(trading_pair, amount, decimal_price) if decimal_price * decimal_amount < trading_rule.min_notional_size: self.trigger_event( self.MARKET_ORDER_FAILURE_EVENT_TAG, MarketOrderFailureEvent(self.current_timestamp, order_id, order_type)) raise ValueError( f"Sell order amount {decimal_amount} is lower than the notional size " ) try: exchange_order_id = await self.place_order(order_id, trading_pair, decimal_amount, False, order_type, decimal_price) self.start_tracking_order(order_id=order_id, exchange_order_id=exchange_order_id, trading_pair=trading_pair, order_type=order_type, trade_type=TradeType.SELL, price=decimal_price, amount=decimal_amount) tracked_order = self._in_flight_orders.get(order_id) if tracked_order is not None: self.logger().info( f"Created {order_type.name.upper()} sell order {order_id} for {decimal_amount} {trading_pair}." ) self.trigger_event( self.MARKET_SELL_ORDER_CREATED_EVENT_TAG, SellOrderCreatedEvent(self.current_timestamp, order_type, trading_pair, decimal_amount, decimal_price, order_id)) except asyncio.CancelledError: raise except Exception as ex: self.stop_tracking_order(order_id) order_type_str = order_type.name.lower() self.logger().network( f"Error submitting sell {order_type_str} order to Mexc for " f"{decimal_amount} {trading_pair} " f"{decimal_price if order_type is OrderType.LIMIT else ''}." f"{decimal_price}." + ",ex:" + repr(ex), exc_info=True, app_warning_msg= "Failed to submit sell order to Mexc. Check API key and network connection." ) self.trigger_event( self.MARKET_ORDER_FAILURE_EVENT_TAG, MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
def test_order_fills_after_cancellation(self): # TODO self.clock.backtest_til(self.start_timestamp + 5) bid_order: LimitOrder = self.strategy.active_bids[0][1] ask_order: LimitOrder = self.strategy.active_asks[0][1] self.assertEqual(Decimal("0.99452"), bid_order.price) self.assertEqual(Decimal("1.0056"), ask_order.price) self.assertEqual(Decimal("3.0"), bid_order.quantity) self.assertEqual(Decimal("3.0"), ask_order.quantity) self.taker_market.trigger_event( MarketEvent.BuyOrderCreated, BuyOrderCreatedEvent(self.start_timestamp + 5, OrderType.LIMIT, bid_order.trading_pair, bid_order.quantity, bid_order.price, bid_order.client_order_id)) self.taker_market.trigger_event( MarketEvent.SellOrderCreated, SellOrderCreatedEvent(self.start_timestamp + 5, OrderType.LIMIT, ask_order.trading_pair, ask_order.quantity, ask_order.price, ask_order.client_order_id)) self.simulate_order_book_widening(self.taker_data.order_book, 0.99, 1.01) self.clock.backtest_til(self.start_timestamp + 10) self.assertEqual(2, len(self.cancel_order_logger.event_log)) self.assertEqual(1, len(self.strategy.active_bids)) self.assertEqual(1, len(self.strategy.active_asks)) bid_order = self.strategy.active_bids[0][1] ask_order = self.strategy.active_asks[0][1] self.assertEqual(Decimal("0.98457"), bid_order.price) self.assertEqual(Decimal("1.0156"), ask_order.price) self.clock.backtest_til(self.start_timestamp + 20) self.simulate_limit_order_fill(self.maker_market, bid_order) self.simulate_limit_order_fill(self.maker_market, ask_order) self.clock.backtest_til(self.start_timestamp + 25) fill_events: List[ OrderFilledEvent] = self.taker_order_fill_logger.event_log bid_hedges: List[OrderFilledEvent] = [ evt for evt in fill_events if evt.trade_type is TradeType.SELL ] ask_hedges: List[OrderFilledEvent] = [ evt for evt in fill_events if evt.trade_type is TradeType.BUY ] self.assertEqual(1, len(bid_hedges)) self.assertEqual(1, len(ask_hedges)) self.assertGreater( self.maker_market.get_balance(self.maker_trading_pairs[2]) + self.taker_market.get_balance(self.taker_trading_pairs[2]), Decimal("10"), ) self.assertEqual(2, len(self.taker_order_fill_logger.event_log)) taker_fill1: OrderFilledEvent = self.taker_order_fill_logger.event_log[ 0] self.assertEqual(TradeType.SELL, taker_fill1.trade_type) self.assertAlmostEqual(Decimal("0.9895"), taker_fill1.price) self.assertAlmostEqual(Decimal("3.0"), taker_fill1.amount) taker_fill2: OrderFilledEvent = self.taker_order_fill_logger.event_log[ 1] self.assertEqual(TradeType.BUY, taker_fill2.trade_type) self.assertAlmostEqual(Decimal("1.0105"), taker_fill2.price) self.assertAlmostEqual(Decimal("3.0"), taker_fill2.amount)
def simulate_limit_order_fill(market: Market, limit_order: LimitOrder): quote_currency_traded: Decimal = limit_order.price * limit_order.quantity base_currency_traded: Decimal = limit_order.quantity quote_currency: str = limit_order.quote_currency base_currency: str = limit_order.base_currency config: MarketConfig = market.config if limit_order.is_buy: market.set_balance( quote_currency, market.get_balance(quote_currency) - quote_currency_traded) market.set_balance( base_currency, market.get_balance(base_currency) + base_currency_traded) market.trigger_event( MarketEvent.BuyOrderCreated, BuyOrderCreatedEvent(market.current_timestamp, OrderType.LIMIT, limit_order.trading_pair, limit_order.quantity, limit_order.price, limit_order.client_order_id)) market.trigger_event( MarketEvent.OrderFilled, OrderFilledEvent( market.current_timestamp, limit_order.client_order_id, limit_order.trading_pair, TradeType.BUY, OrderType.LIMIT, limit_order.price, limit_order.quantity, TradeFee(Decimal(0)), ), ) market.trigger_event( MarketEvent.BuyOrderCompleted, BuyOrderCompletedEvent( market.current_timestamp, limit_order.client_order_id, base_currency, quote_currency, base_currency if config.buy_fees_asset is AssetType.BASE_CURRENCY else quote_currency, base_currency_traded, quote_currency_traded, Decimal(0), OrderType.LIMIT, ), ) else: market.set_balance( quote_currency, market.get_balance(quote_currency) + quote_currency_traded) market.set_balance( base_currency, market.get_balance(base_currency) - base_currency_traded) market.trigger_event( MarketEvent.BuyOrderCreated, SellOrderCreatedEvent(market.current_timestamp, OrderType.LIMIT, limit_order.trading_pair, limit_order.quantity, limit_order.price, limit_order.client_order_id)) market.trigger_event( MarketEvent.OrderFilled, OrderFilledEvent( market.current_timestamp, limit_order.client_order_id, limit_order.trading_pair, TradeType.SELL, OrderType.LIMIT, limit_order.price, limit_order.quantity, TradeFee(Decimal(0)), ), ) market.trigger_event( MarketEvent.SellOrderCompleted, SellOrderCompletedEvent( market.current_timestamp, limit_order.client_order_id, base_currency, quote_currency, base_currency if config.sell_fees_asset is AssetType.BASE_CURRENCY else quote_currency, base_currency_traded, quote_currency_traded, Decimal(0), OrderType.LIMIT, ), )
def simulate_limit_order_fill(market: MockPaperExchange, limit_order: LimitOrder): quote_currency_traded: Decimal = limit_order.price * limit_order.quantity base_currency_traded: Decimal = limit_order.quantity quote_currency: str = limit_order.quote_currency base_currency: str = limit_order.base_currency if limit_order.is_buy: market.set_balance( quote_currency, market.get_balance(quote_currency) - quote_currency_traded) market.set_balance( base_currency, market.get_balance(base_currency) + base_currency_traded) market.trigger_event( MarketEvent.BuyOrderCreated, BuyOrderCreatedEvent(market.current_timestamp, OrderType.LIMIT, limit_order.trading_pair, limit_order.quantity, limit_order.price, limit_order.client_order_id, limit_order.creation_timestamp * 1e-6)) market.trigger_event( MarketEvent.OrderFilled, OrderFilledEvent( market.current_timestamp, limit_order.client_order_id, limit_order.trading_pair, TradeType.BUY, OrderType.LIMIT, limit_order.price, limit_order.quantity, AddedToCostTradeFee(Decimal(0)), ), ) market.trigger_event( MarketEvent.BuyOrderCompleted, BuyOrderCompletedEvent( market.current_timestamp, limit_order.client_order_id, base_currency, quote_currency, base_currency_traded, quote_currency_traded, OrderType.LIMIT, ), ) else: market.set_balance( quote_currency, market.get_balance(quote_currency) + quote_currency_traded) market.set_balance( base_currency, market.get_balance(base_currency) - base_currency_traded) market.trigger_event( MarketEvent.BuyOrderCreated, SellOrderCreatedEvent( market.current_timestamp, OrderType.LIMIT, limit_order.trading_pair, limit_order.quantity, limit_order.price, limit_order.client_order_id, limit_order.creation_timestamp * 1e-6, )) market.trigger_event( MarketEvent.OrderFilled, OrderFilledEvent( market.current_timestamp, limit_order.client_order_id, limit_order.trading_pair, TradeType.SELL, OrderType.LIMIT, limit_order.price, limit_order.quantity, AddedToCostTradeFee(Decimal(0)), ), ) market.trigger_event( MarketEvent.SellOrderCompleted, SellOrderCompletedEvent( market.current_timestamp, limit_order.client_order_id, base_currency, quote_currency, base_currency_traded, quote_currency_traded, OrderType.LIMIT, ), )